mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-01-17 03:18:07 +00:00
record message
auto download attached files setting
This commit is contained in:
parent
6d4506c5ae
commit
f82931d6c6
49 changed files with 3097 additions and 1984 deletions
|
|
@ -78,6 +78,7 @@
|
|||
#include "core/payload-type/PayloadTypeProxy.hpp"
|
||||
#include "core/phone-number/PhoneNumber.hpp"
|
||||
#include "core/phone-number/PhoneNumberProxy.hpp"
|
||||
#include "core/recorder/RecorderGui.hpp"
|
||||
#include "core/register/RegisterPage.hpp"
|
||||
#include "core/screen/ScreenList.hpp"
|
||||
#include "core/screen/ScreenProxy.hpp"
|
||||
|
|
@ -686,6 +687,7 @@ void App::initCppInterfaces() {
|
|||
qmlRegisterType<FPSCounter>(Constants::MainQmlUri, 1, 0, "FPSCounter");
|
||||
qmlRegisterType<EmojiModel>(Constants::MainQmlUri, 1, 0, "EmojiModel");
|
||||
qmlRegisterType<SoundPlayerGui>(Constants::MainQmlUri, 1, 0, "SoundPlayerGui");
|
||||
qmlRegisterType<RecorderGui>(Constants::MainQmlUri, 1, 0, "RecorderGui");
|
||||
|
||||
qmlRegisterType<TimeZoneProxy>(Constants::MainQmlUri, 1, 0, "TimeZoneProxy");
|
||||
|
||||
|
|
|
|||
|
|
@ -86,6 +86,9 @@ list(APPEND _LINPHONEAPP_SOURCES
|
|||
core/sound-player/SoundPlayerCore.cpp
|
||||
core/sound-player/SoundPlayerGui.cpp
|
||||
|
||||
core/recorder/RecorderCore.cpp
|
||||
core/recorder/RecorderGui.cpp
|
||||
|
||||
core/videoSource/VideoSourceDescriptorCore.cpp
|
||||
core/videoSource/VideoSourceDescriptorGui.cpp
|
||||
|
||||
|
|
|
|||
|
|
@ -117,6 +117,15 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
|
|||
mChatModelConnection->makeConnectToCore(&ChatCore::lDeleteHistory, [this]() {
|
||||
mChatModelConnection->invokeToModel([this]() { mChatModel->deleteHistory(); });
|
||||
});
|
||||
mChatModelConnection->makeConnectToCore(&ChatCore::lDeleteMessage, [this](ChatMessageGui *message) {
|
||||
mChatModelConnection->invokeToModel([this, core = message ? message->mCore : nullptr]() {
|
||||
auto messageModel = core ? core->getModel() : nullptr;
|
||||
if (messageModel) {
|
||||
mChatModel->deleteMessage(messageModel->getMonitor());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
mChatModelConnection->makeConnectToCore(
|
||||
&ChatCore::lLeave, [this]() { mChatModelConnection->invokeToModel([this]() { mChatModel->leave(); }); });
|
||||
mChatModelConnection->makeConnectToModel(&ChatModel::historyDeleted, [this]() {
|
||||
|
|
@ -458,8 +467,13 @@ void ChatCore::appendEventLogsToEventLogList(QList<QSharedPointer<EventLogCore>>
|
|||
|
||||
void ChatCore::appendEventLogToEventLogList(QSharedPointer<EventLogCore> e) {
|
||||
if (mEventLogList.contains(e)) return;
|
||||
mEventLogList.append(e);
|
||||
emit eventsInserted({e});
|
||||
auto it = std::find_if(mEventLogList.begin(), mEventLogList.end(), [e](QSharedPointer<EventLogCore> event) {
|
||||
return e->getEventLogId() == event->getEventLogId();
|
||||
});
|
||||
if (it == mEventLogList.end()) {
|
||||
mEventLogList.append(e);
|
||||
emit eventsInserted({e});
|
||||
}
|
||||
}
|
||||
|
||||
void ChatCore::removeEventLogsFromEventLogList(QList<QSharedPointer<EventLogCore>> list) {
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ signals:
|
|||
void meAdminChanged();
|
||||
void participantsChanged();
|
||||
|
||||
void lDeleteMessage();
|
||||
void lDeleteMessage(ChatMessageGui *message);
|
||||
void lDelete();
|
||||
void lDeleteHistory();
|
||||
void lMarkAsRead();
|
||||
|
|
@ -159,6 +159,7 @@ signals:
|
|||
void lUpdateLastUpdatedTime();
|
||||
void lSendTextMessage(QString message);
|
||||
void lSendMessage(QString message, QVariantList files);
|
||||
void lSendVoiceMessage();
|
||||
void lCompose();
|
||||
void lLeave();
|
||||
void lSetMuted(bool muted);
|
||||
|
|
|
|||
|
|
@ -90,6 +90,12 @@ ChatMessageCore::ChatMessageCore(const std::shared_ptr<linphone::ChatMessage> &c
|
|||
for (auto content : chatmessage->getContents()) {
|
||||
auto contentCore = ChatMessageContentCore::create(content, mChatMessageModel);
|
||||
mChatMessageContentList.push_back(contentCore);
|
||||
if (content->isFile() && !content->isVoiceRecording()) mHasFileContent = true;
|
||||
if (content->isIcalendar()) mIsCalendarInvite = true;
|
||||
if (content->isVoiceRecording()) {
|
||||
mIsVoiceRecording = true;
|
||||
mVoiceRecordingContent = contentCore;
|
||||
}
|
||||
}
|
||||
auto reac = chatmessage->getOwnReaction();
|
||||
mOwnReaction = reac ? Utils::coreStringToAppString(reac->getBody()) : QString();
|
||||
|
|
@ -122,11 +128,6 @@ ChatMessageCore::ChatMessageCore(const std::shared_ptr<linphone::ChatMessage> &c
|
|||
|
||||
mIsForward = chatmessage->isForward();
|
||||
mIsReply = chatmessage->isReply();
|
||||
for (auto &content : chatmessage->getContents()) {
|
||||
if (content->isFile() && !content->isVoiceRecording()) mHasFileContent = true;
|
||||
if (content->isIcalendar()) mIsCalendarInvite = true;
|
||||
if (content->isVoiceRecording()) mIsVoiceRecording = true;
|
||||
}
|
||||
}
|
||||
|
||||
ChatMessageCore::~ChatMessageCore() {
|
||||
|
|
@ -156,6 +157,9 @@ void ChatMessageCore::setSelf(QSharedPointer<ChatMessageCore> me) {
|
|||
mChatMessageModelConnection->makeConnectToCore(&ChatMessageCore::lRemoveReaction, [this]() {
|
||||
mChatMessageModelConnection->invokeToModel([this] { mChatMessageModel->removeReaction(); });
|
||||
});
|
||||
mChatMessageModelConnection->makeConnectToCore(&ChatMessageCore::lSend, [this]() {
|
||||
mChatMessageModelConnection->invokeToModel([this] { mChatMessageModel->send(); });
|
||||
});
|
||||
mChatMessageModelConnection->makeConnectToModel(
|
||||
&ChatMessageModel::newMessageReaction,
|
||||
[this](const std::shared_ptr<linphone::ChatMessage> &message,
|
||||
|
|
@ -431,3 +435,7 @@ std::shared_ptr<ChatMessageModel> ChatMessageCore::getModel() const {
|
|||
// ConferenceInfoGui *ChatMessageCore::getConferenceInfoGui() const {
|
||||
// return mConferenceInfo ? new ConferenceInfoGui(mConferenceInfo) : nullptr;
|
||||
// }
|
||||
|
||||
ChatMessageContentGui *ChatMessageCore::getVoiceRecordingContent() const {
|
||||
return new ChatMessageContentGui(mVoiceRecordingContent);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CHATMESSAGECORE_H_
|
||||
#define CHATMESSAGECORE_H_
|
||||
#ifndef CHAT_MESSAGE_CORE_H_
|
||||
#define CHAT_MESSAGE_CORE_H_
|
||||
|
||||
#include "EventLogCore.hpp"
|
||||
#include "core/chat/message/content/ChatMessageContentCore.hpp"
|
||||
#include "core/chat/message/content/ChatMessageContentGui.hpp"
|
||||
#include "core/chat/message/content/ChatMessageContentProxy.hpp"
|
||||
#include "core/conference/ConferenceInfoCore.hpp"
|
||||
#include "core/conference/ConferenceInfoGui.hpp"
|
||||
|
|
@ -118,7 +118,7 @@ public:
|
|||
void setMessageState(LinphoneEnums::ChatMessageState state);
|
||||
|
||||
std::shared_ptr<ChatMessageModel> getModel() const;
|
||||
// ConferenceInfoGui *getConferenceInfoGui() const;
|
||||
Q_INVOKABLE ChatMessageContentGui *getVoiceRecordingContent() const;
|
||||
|
||||
signals:
|
||||
void timestampChanged(QDateTime timestamp);
|
||||
|
|
@ -136,6 +136,7 @@ signals:
|
|||
void readChanged();
|
||||
void lSendReaction(const QString &reaction);
|
||||
void lRemoveReaction();
|
||||
void lSend();
|
||||
|
||||
private:
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
|
|
@ -164,10 +165,12 @@ private:
|
|||
bool mIsOutgoing = false;
|
||||
LinphoneEnums::ChatMessageState mMessageState;
|
||||
QList<QSharedPointer<ChatMessageContentCore>> mChatMessageContentList;
|
||||
// for voice recording creation message
|
||||
QSharedPointer<ChatMessageContentCore> mVoiceRecordingContent;
|
||||
// QSharedPointer<ConferenceInfoCore> mConferenceInfo = nullptr;
|
||||
|
||||
std::shared_ptr<ChatMessageModel> mChatMessageModel;
|
||||
QSharedPointer<SafeConnection<ChatMessageCore, ChatMessageModel>> mChatMessageModelConnection;
|
||||
};
|
||||
|
||||
#endif // CHATMESSAGECORE_H_
|
||||
#endif // CHAT_MESSAGE_CORE_H_
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ bool ChatMessageContentProxy::SortFilterList::filterAcceptsRow(int sourceRow, co
|
|||
if (contentCore) {
|
||||
if (mFilterType == (int)FilterContentType::Unknown) return false;
|
||||
else if (mFilterType == (int)FilterContentType::File) {
|
||||
return contentCore->isFile() || contentCore->isFileTransfer();
|
||||
return !contentCore->isVoiceRecording() && (contentCore->isFile() || contentCore->isFileTransfer());
|
||||
} else if (mFilterType == (int)FilterContentType::Text) return contentCore->isText();
|
||||
else if (mFilterType == (int)FilterContentType::Voice) return contentCore->isVoiceRecording();
|
||||
else if (mFilterType == (int)FilterContentType::Conference) return contentCore->isCalendar();
|
||||
|
|
|
|||
|
|
@ -324,7 +324,6 @@ void Notifier::notifyReceivedMessages(const std::shared_ptr<linphone::ChatRoom>
|
|||
|
||||
if (messages.size() > 0) {
|
||||
shared_ptr<linphone::ChatMessage> message = messages.front();
|
||||
|
||||
auto receiverAccount = ToolModel::findAccount(message->getToAddress());
|
||||
if (receiverAccount) {
|
||||
auto senderAccount = ToolModel::findAccount(message->getFromAddress());
|
||||
|
|
@ -340,7 +339,8 @@ void Notifier::notifyReceivedMessages(const std::shared_ptr<linphone::ChatRoom>
|
|||
}
|
||||
}
|
||||
|
||||
if (messages.size() == 1) { // Display only sender on mono message.
|
||||
auto getMessage = [this, &remoteAddress, &txt](const shared_ptr<linphone::ChatMessage> &message) {
|
||||
if (message->isRead()) return;
|
||||
auto remoteAddr = message->getFromAddress()->clone();
|
||||
remoteAddr->clean();
|
||||
remoteAddress = Utils::coreStringToAppString(remoteAddr->asStringUriOnly());
|
||||
|
|
@ -356,9 +356,22 @@ void Notifier::notifyReceivedMessages(const std::shared_ptr<linphone::ChatRoom>
|
|||
if (txt.isEmpty() && message->hasConferenceInvitationContent())
|
||||
//: 'Conference invitation received!' : Notification about receiving an invitation to a conference.
|
||||
txt = tr("new_conference_invitation");
|
||||
};
|
||||
|
||||
if (messages.size() == 1) { // Display only sender on mono message.
|
||||
getMessage(message);
|
||||
} else {
|
||||
//: 'New messages received!' Notification that warn the user of new messages.
|
||||
txt = tr("new_chat_room_messages");
|
||||
int unreadCount = 0;
|
||||
for (auto &message : messages) {
|
||||
if (!message->isRead()) {
|
||||
++unreadCount;
|
||||
if (unreadCount == 1) getMessage(message);
|
||||
}
|
||||
}
|
||||
if (unreadCount == 0) return;
|
||||
if (unreadCount > 1)
|
||||
//: 'New messages received!' Notification that warn the user of new messages.
|
||||
txt = tr("new_chat_room_messages");
|
||||
}
|
||||
|
||||
auto chatCore = ChatCore::create(room);
|
||||
|
|
|
|||
150
Linphone/core/recorder/RecorderCore.cpp
Normal file
150
Linphone/core/recorder/RecorderCore.cpp
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#include "core/App.hpp"
|
||||
#include "core/path/Paths.hpp"
|
||||
#include "model/core/CoreModel.hpp"
|
||||
#include "model/setting/SettingsModel.hpp"
|
||||
#include "tool/Utils.hpp"
|
||||
|
||||
#include "RecorderCore.hpp"
|
||||
|
||||
DEFINE_ABSTRACT_OBJECT(RecorderCore)
|
||||
|
||||
// =============================================================================
|
||||
|
||||
QSharedPointer<RecorderCore> RecorderCore::create(QObject *parent) {
|
||||
auto sharedPointer = QSharedPointer<RecorderCore>(new RecorderCore(), &QObject::deleteLater);
|
||||
sharedPointer->setSelf(sharedPointer);
|
||||
sharedPointer->moveToThread(App::getInstance()->thread());
|
||||
return sharedPointer;
|
||||
}
|
||||
|
||||
RecorderCore::RecorderCore(QObject *parent) : QObject(parent) {
|
||||
App::getInstance()->mEngine->setObjectOwnership(
|
||||
this, QQmlEngine::CppOwnership); // Avoid QML to destroy it when passing by Q_INVOKABLE
|
||||
}
|
||||
|
||||
RecorderCore::~RecorderCore() {
|
||||
}
|
||||
|
||||
void RecorderCore::buildRecorder(QSharedPointer<RecorderCore> me) {
|
||||
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
|
||||
auto core = CoreModel::getInstance()->getCore();
|
||||
std::shared_ptr<linphone::RecorderParams> params = core->createRecorderParams();
|
||||
params->setFileFormat(linphone::MediaFileFormat::Mkv);
|
||||
params->setVideoCodec("");
|
||||
auto recorder = core->createRecorder(params);
|
||||
if (recorder) {
|
||||
mDuration = recorder->getDuration();
|
||||
mCaptureVolume = recorder->getCaptureVolume();
|
||||
if (mRecorderModelConnection) mRecorderModelConnection->disconnect();
|
||||
mRecorderModel = Utils::makeQObject_ptr<RecorderModel>(recorder);
|
||||
mRecorderModelConnection = SafeConnection<RecorderCore, RecorderModel>::create(me, mRecorderModel);
|
||||
mRecorderModelConnection->makeConnectToCore(&RecorderCore::lStart, [this] {
|
||||
mRecorderModelConnection->invokeToModel([this] { mRecorderModel->start(); });
|
||||
});
|
||||
mRecorderModelConnection->makeConnectToCore(&RecorderCore::lPause, [this] {
|
||||
mRecorderModelConnection->invokeToModel([this] { mRecorderModel->pause(); });
|
||||
});
|
||||
mRecorderModelConnection->makeConnectToCore(&RecorderCore::lStop, [this] {
|
||||
mRecorderModelConnection->invokeToModel([this] { mRecorderModel->stop(); });
|
||||
});
|
||||
mRecorderModelConnection->makeConnectToCore(&RecorderCore::lRefresh, [this] {
|
||||
mRecorderModelConnection->invokeToModel([this] {
|
||||
auto duration = mRecorderModel->getDuration();
|
||||
auto volume = mRecorderModel->getCaptureVolume();
|
||||
mRecorderModelConnection->invokeToModel([this, duration, volume] {
|
||||
setDuration(duration);
|
||||
setCaptureVolume(volume);
|
||||
});
|
||||
});
|
||||
});
|
||||
mRecorderModelConnection->makeConnectToModel(&RecorderModel::stateChanged, [this] {
|
||||
auto state = LinphoneEnums::fromLinphone(mRecorderModel->getState());
|
||||
mRecorderModelConnection->invokeToCore([this, state] { setState(state); });
|
||||
});
|
||||
mRecorderModelConnection->makeConnectToModel(&RecorderModel::fileChanged, [this] {
|
||||
auto file = mRecorderModel->getFile();
|
||||
mRecorderModelConnection->invokeToCore([this, file] { setFile(file); });
|
||||
});
|
||||
mRecorderModelConnection->makeConnectToModel(&RecorderModel::errorChanged, [this](QString error) {
|
||||
mRecorderModelConnection->invokeToCore([this, error] { emit errorChanged(error); });
|
||||
});
|
||||
emit ready();
|
||||
}
|
||||
}
|
||||
|
||||
void RecorderCore::setSelf(QSharedPointer<RecorderCore> me) {
|
||||
auto coreModel = CoreModel::getInstance();
|
||||
|
||||
mCoreModelConnection = SafeConnection<RecorderCore, CoreModel>::create(me, coreModel);
|
||||
mCoreModelConnection->invokeToModel([this, me, coreModel] { buildRecorder(me); });
|
||||
}
|
||||
|
||||
void RecorderCore::setCaptureVolume(float volume) {
|
||||
if (mCaptureVolume != volume) {
|
||||
mCaptureVolume = volume;
|
||||
emit captureVolumeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
int RecorderCore::getDuration() const {
|
||||
return mDuration;
|
||||
}
|
||||
|
||||
void RecorderCore::setDuration(int duration) {
|
||||
if (mDuration != duration) {
|
||||
mDuration = duration;
|
||||
emit durationChanged();
|
||||
}
|
||||
}
|
||||
|
||||
float RecorderCore::getCaptureVolume() const {
|
||||
return mCaptureVolume;
|
||||
}
|
||||
|
||||
LinphoneEnums::RecorderState RecorderCore::getState() const {
|
||||
return mState;
|
||||
}
|
||||
|
||||
void RecorderCore::setState(LinphoneEnums::RecorderState state) {
|
||||
if (mState != state) {
|
||||
mState = state;
|
||||
emit stateChanged(state);
|
||||
}
|
||||
}
|
||||
|
||||
QString RecorderCore::getFile() const {
|
||||
return mFile;
|
||||
}
|
||||
|
||||
void RecorderCore::setFile(QString file) {
|
||||
if (mFile != file) {
|
||||
mFile = file;
|
||||
emit fileChanged();
|
||||
}
|
||||
}
|
||||
|
||||
const std::shared_ptr<RecorderModel> &RecorderCore::getModel() const {
|
||||
return mRecorderModel;
|
||||
}
|
||||
82
Linphone/core/recorder/RecorderCore.hpp
Normal file
82
Linphone/core/recorder/RecorderCore.hpp
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef RECORDER_CORE_H
|
||||
#define RECORDER_CORE_H
|
||||
|
||||
#include "model/recorder/RecorderModel.hpp"
|
||||
#include "tool/LinphoneEnums.hpp"
|
||||
#include "tool/thread/SafeConnection.hpp"
|
||||
#include <linphone++/linphone.hh>
|
||||
|
||||
// =============================================================================
|
||||
|
||||
class RecorderCore : public QObject, public AbstractObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static QSharedPointer<RecorderCore> create(QObject *parent = nullptr);
|
||||
RecorderCore(QObject *parent = nullptr);
|
||||
~RecorderCore();
|
||||
void setSelf(QSharedPointer<RecorderCore> me);
|
||||
|
||||
Q_PROPERTY(LinphoneEnums::RecorderState state READ getState NOTIFY stateChanged)
|
||||
Q_PROPERTY(QString file READ getFile NOTIFY fileChanged)
|
||||
Q_PROPERTY(int duration READ getDuration NOTIFY durationChanged)
|
||||
Q_PROPERTY(int captureVolume READ getCaptureVolume NOTIFY captureVolumeChanged)
|
||||
|
||||
void buildRecorder(QSharedPointer<RecorderCore> me);
|
||||
|
||||
int getDuration() const;
|
||||
void setDuration(int duration);
|
||||
float getCaptureVolume() const;
|
||||
void setCaptureVolume(float volume);
|
||||
LinphoneEnums::RecorderState getState() const;
|
||||
void setState(LinphoneEnums::RecorderState state);
|
||||
QString getFile() const;
|
||||
void setFile(QString file);
|
||||
const std::shared_ptr<RecorderModel> &getModel() const;
|
||||
|
||||
signals:
|
||||
void lStart();
|
||||
void lPause();
|
||||
void lStop();
|
||||
void lRefresh();
|
||||
|
||||
void stateChanged(LinphoneEnums::RecorderState state);
|
||||
void fileChanged();
|
||||
void durationChanged();
|
||||
void captureVolumeChanged();
|
||||
void errorChanged(QString error);
|
||||
void ready();
|
||||
|
||||
private:
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
|
||||
std::shared_ptr<RecorderModel> mRecorderModel;
|
||||
QString mFile;
|
||||
LinphoneEnums::RecorderState mState;
|
||||
int mDuration = 0;
|
||||
int mCaptureVolume = 0;
|
||||
bool mIsReady = false;
|
||||
QSharedPointer<SafeConnection<RecorderCore, RecorderModel>> mRecorderModelConnection;
|
||||
QSharedPointer<SafeConnection<RecorderCore, CoreModel>> mCoreModelConnection;
|
||||
};
|
||||
#endif
|
||||
49
Linphone/core/recorder/RecorderGui.cpp
Normal file
49
Linphone/core/recorder/RecorderGui.cpp
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2024 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "RecorderGui.hpp"
|
||||
#include "core/App.hpp"
|
||||
|
||||
DEFINE_ABSTRACT_OBJECT(RecorderGui)
|
||||
|
||||
RecorderGui::RecorderGui(QObject *parent) : QObject(parent) {
|
||||
mustBeInMainThread(getClassName());
|
||||
mCore = RecorderCore::create();
|
||||
if (mCore) connect(mCore.get(), &RecorderCore::errorChanged, this, &RecorderGui::errorChanged);
|
||||
if (mCore) connect(mCore.get(), &RecorderCore::stateChanged, this, &RecorderGui::stateChanged);
|
||||
if (mCore) connect(mCore.get(), &RecorderCore::ready, this, &RecorderGui::ready);
|
||||
}
|
||||
RecorderGui::RecorderGui(QSharedPointer<RecorderCore> core) {
|
||||
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership);
|
||||
mCore = core;
|
||||
if (isInLinphoneThread()) moveToThread(App::getInstance()->thread());
|
||||
}
|
||||
|
||||
LinphoneEnums::RecorderState RecorderGui::getState() const {
|
||||
return mCore ? mCore->getState() : LinphoneEnums::RecorderState::Closed;
|
||||
}
|
||||
|
||||
RecorderGui::~RecorderGui() {
|
||||
mustBeInMainThread("~" + getClassName());
|
||||
}
|
||||
|
||||
RecorderCore *RecorderGui::getCore() const {
|
||||
return mCore.get();
|
||||
}
|
||||
52
Linphone/core/recorder/RecorderGui.hpp
Normal file
52
Linphone/core/recorder/RecorderGui.hpp
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2024 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef RECORDER_GUI_H_
|
||||
#define RECORDER_GUI_H_
|
||||
|
||||
#include "RecorderCore.hpp"
|
||||
#include "tool/AbstractObject.hpp"
|
||||
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
|
||||
class RecorderGui : public QObject, public AbstractObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(RecorderCore *core READ getCore CONSTANT)
|
||||
|
||||
public:
|
||||
RecorderGui(QObject *parent = nullptr);
|
||||
RecorderGui(QSharedPointer<RecorderCore> core);
|
||||
~RecorderGui();
|
||||
RecorderCore *getCore() const;
|
||||
LinphoneEnums::RecorderState getState() const;
|
||||
QSharedPointer<RecorderCore> mCore;
|
||||
|
||||
signals:
|
||||
void errorChanged(QString error);
|
||||
void ready();
|
||||
void stateChanged(LinphoneEnums::RecorderState state);
|
||||
|
||||
private:
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -49,6 +49,7 @@ SettingsCore::SettingsCore(QObject *parent) : QObject(parent) {
|
|||
// Call
|
||||
mVideoEnabled = settingsModel->getVideoEnabled();
|
||||
mEchoCancellationEnabled = settingsModel->getEchoCancellationEnabled();
|
||||
mAutoDownloadReceivedFiles = settingsModel->getAutoDownloadReceivedFiles();
|
||||
mAutomaticallyRecordCallsEnabled = settingsModel->getAutomaticallyRecordCallsEnabled();
|
||||
|
||||
// Audio
|
||||
|
|
@ -143,6 +144,7 @@ SettingsCore::SettingsCore(const SettingsCore &settingsCore) {
|
|||
// Call
|
||||
mVideoEnabled = settingsCore.mVideoEnabled;
|
||||
mEchoCancellationEnabled = settingsCore.mEchoCancellationEnabled;
|
||||
mAutoDownloadReceivedFiles = settingsCore.mAutoDownloadReceivedFiles;
|
||||
mAutomaticallyRecordCallsEnabled = settingsCore.mAutomaticallyRecordCallsEnabled;
|
||||
|
||||
// Audio
|
||||
|
|
@ -233,6 +235,12 @@ void SettingsCore::setSelf(QSharedPointer<SettingsCore> me) {
|
|||
mSettingsModelConnection->invokeToCore([this, enabled]() { setEchoCancellationEnabled(enabled); });
|
||||
});
|
||||
|
||||
// Auto download incoming files
|
||||
mSettingsModelConnection->makeConnectToModel(
|
||||
&SettingsModel::autoDownloadReceivedFilesChanged, [this](const bool enabled) {
|
||||
mSettingsModelConnection->invokeToCore([this, enabled]() { setAutoDownloadReceivedFiles(enabled); });
|
||||
});
|
||||
|
||||
// Auto recording
|
||||
mSettingsModelConnection->makeConnectToModel(
|
||||
&SettingsModel::automaticallyRecordCallsEnabledChanged, [this](const bool enabled) {
|
||||
|
|
@ -462,6 +470,7 @@ void SettingsCore::reset(const SettingsCore &settingsCore) {
|
|||
setEchoCancellationEnabled(settingsCore.mEchoCancellationEnabled);
|
||||
setAutomaticallyRecordCallsEnabled(settingsCore.mAutomaticallyRecordCallsEnabled);
|
||||
|
||||
setAutoDownloadReceivedFiles(settingsCore.mAutoDownloadReceivedFiles);
|
||||
// Audio
|
||||
setCaptureDevices(settingsCore.mCaptureDevices);
|
||||
setPlaybackDevices(settingsCore.mPlaybackDevices);
|
||||
|
|
@ -576,6 +585,14 @@ void SettingsCore::setEchoCancellationEnabled(bool enabled) {
|
|||
}
|
||||
}
|
||||
|
||||
void SettingsCore::setAutoDownloadReceivedFiles(bool enabled) {
|
||||
if (mAutoDownloadReceivedFiles != enabled) {
|
||||
mAutoDownloadReceivedFiles = enabled;
|
||||
emit autoDownloadReceivedFilesChanged();
|
||||
setIsSaved(false);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsCore::setAutomaticallyRecordCallsEnabled(bool enabled) {
|
||||
if (mAutomaticallyRecordCallsEnabled != enabled) {
|
||||
mAutomaticallyRecordCallsEnabled = enabled;
|
||||
|
|
@ -960,6 +977,9 @@ void SettingsCore::writeIntoModel(std::shared_ptr<SettingsModel> model) const {
|
|||
model->setEchoCancellationEnabled(mEchoCancellationEnabled);
|
||||
model->setAutomaticallyRecordCallsEnabled(mAutomaticallyRecordCallsEnabled);
|
||||
|
||||
// Chat
|
||||
model->setAutoDownloadReceivedFiles(mAutoDownloadReceivedFiles);
|
||||
|
||||
// Audio
|
||||
model->setRingerDevice(mRingerDevice);
|
||||
model->setCaptureDevice(mCaptureDevice);
|
||||
|
|
@ -1022,6 +1042,9 @@ void SettingsCore::writeFromModel(const std::shared_ptr<SettingsModel> &model) {
|
|||
mEchoCancellationEnabled = model->getEchoCancellationEnabled();
|
||||
mAutomaticallyRecordCallsEnabled = model->getAutomaticallyRecordCallsEnabled();
|
||||
|
||||
// Chat
|
||||
mAutoDownloadReceivedFiles = model->getAutoDownloadReceivedFiles();
|
||||
|
||||
// Audio
|
||||
mCaptureDevices = model->getCaptureDevices();
|
||||
mPlaybackDevices = model->getPlaybackDevices();
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ public:
|
|||
Q_PROPERTY(bool videoEnabled READ getVideoEnabled WRITE setVideoEnabled NOTIFY videoEnabledChanged)
|
||||
Q_PROPERTY(bool echoCancellationEnabled READ getEchoCancellationEnabled WRITE setEchoCancellationEnabled NOTIFY
|
||||
echoCancellationEnabledChanged)
|
||||
Q_PROPERTY(bool autoDownloadReceivedFiles READ getAutoDownloadReceivedFiles WRITE setAutoDownloadReceivedFiles
|
||||
NOTIFY autoDownloadReceivedFilesChanged)
|
||||
Q_PROPERTY(
|
||||
int echoCancellationCalibration READ getEchoCancellationCalibration NOTIFY echoCancellationCalibrationChanged)
|
||||
Q_PROPERTY(bool automaticallyRecordCallsEnabled READ getAutomaticallyRecordCallsEnabled WRITE
|
||||
|
|
@ -127,6 +129,11 @@ public:
|
|||
}
|
||||
void setEchoCancellationEnabled(bool enabled);
|
||||
|
||||
bool getAutoDownloadReceivedFiles() {
|
||||
return mAutoDownloadReceivedFiles;
|
||||
}
|
||||
void setAutoDownloadReceivedFiles(bool enabled);
|
||||
|
||||
bool getAutomaticallyRecordCallsEnabled() {
|
||||
return mAutomaticallyRecordCallsEnabled;
|
||||
}
|
||||
|
|
@ -248,6 +255,7 @@ signals:
|
|||
void videoEnabledChanged();
|
||||
|
||||
void echoCancellationEnabledChanged();
|
||||
void autoDownloadReceivedFilesChanged();
|
||||
|
||||
void automaticallyRecordCallsEnabledChanged();
|
||||
|
||||
|
|
@ -327,6 +335,7 @@ private:
|
|||
// Call
|
||||
bool mVideoEnabled;
|
||||
bool mEchoCancellationEnabled;
|
||||
bool mAutoDownloadReceivedFiles;
|
||||
bool mAutomaticallyRecordCallsEnabled;
|
||||
|
||||
// Audio
|
||||
|
|
|
|||
|
|
@ -29,14 +29,6 @@
|
|||
|
||||
DEFINE_ABSTRACT_OBJECT(SoundPlayerCore)
|
||||
|
||||
// =============================================================================
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace {
|
||||
int ForceCloseTimerInterval = 20;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
QSharedPointer<SoundPlayerCore> SoundPlayerCore::create() {
|
||||
|
|
@ -114,7 +106,7 @@ void SoundPlayerCore::buildInternalPlayer(QSharedPointer<SoundPlayerCore> me) {
|
|||
mSoundPlayerModelConnection->invokeToModel([this] { mSoundPlayerModel->play(mSource); });
|
||||
});
|
||||
mSoundPlayerModelConnection->makeConnectToCore(&SoundPlayerCore::lSeek, [this](int offset) {
|
||||
mSoundPlayerModelConnection->invokeToModel([this, offset] { mSoundPlayerModel->seek(offset); });
|
||||
mSoundPlayerModelConnection->invokeToModel([this, offset] { mSoundPlayerModel->seek(mSource, offset); });
|
||||
});
|
||||
mSoundPlayerModelConnection->makeConnectToModel(&SoundPlayerModel::positionChanged, [this](int pos) {
|
||||
mSoundPlayerModelConnection->invokeToCore([this, pos] { setPosition(pos); });
|
||||
|
|
@ -126,12 +118,15 @@ void SoundPlayerCore::buildInternalPlayer(QSharedPointer<SoundPlayerCore> me) {
|
|||
});
|
||||
});
|
||||
mSoundPlayerModelConnection->makeConnectToModel(&SoundPlayerModel::eofReached,
|
||||
[this](const shared_ptr<linphone::Player> &player) {
|
||||
[this](const std::shared_ptr<linphone::Player> &player) {
|
||||
mSoundPlayerModelConnection->invokeToCore([this] {
|
||||
mForceClose = true;
|
||||
handleEof();
|
||||
});
|
||||
});
|
||||
mSoundPlayerModelConnection->makeConnectToModel(&SoundPlayerModel::errorChanged, [this](QString error) {
|
||||
mSoundPlayerModelConnection->invokeToCore([this, error] { setError(error); });
|
||||
});
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ SoundPlayerGui::SoundPlayerGui(QObject *parent) : QObject(parent) {
|
|||
if (mCore) connect(mCore.get(), &SoundPlayerCore::sourceChanged, this, &SoundPlayerGui::sourceChanged);
|
||||
if (mCore) connect(mCore.get(), &SoundPlayerCore::stopped, this, &SoundPlayerGui::stopped);
|
||||
if (mCore) connect(mCore.get(), &SoundPlayerCore::positionChanged, this, &SoundPlayerGui::positionChanged);
|
||||
if (mCore) connect(mCore.get(), &SoundPlayerCore::errorChanged, this, &SoundPlayerGui::errorChanged);
|
||||
}
|
||||
SoundPlayerGui::SoundPlayerGui(QSharedPointer<SoundPlayerCore> core) {
|
||||
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership);
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ signals:
|
|||
void sourceChanged();
|
||||
void stopped();
|
||||
void positionChanged();
|
||||
void errorChanged(QString error);
|
||||
|
||||
private:
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
|
|
|
|||
|
|
@ -823,6 +823,19 @@
|
|||
"puppy eyes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "1f979",
|
||||
"char": "🥹",
|
||||
"name": "face holding back tears",
|
||||
"keywords": [
|
||||
"tears",
|
||||
"emotive",
|
||||
"admiration",
|
||||
"face with tears",
|
||||
"gratitude",
|
||||
"admiration"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "1f626",
|
||||
"char": "😦",
|
||||
|
|
|
|||
56
Linphone/data/emoji/emojiSvgs/1f979.svg
Normal file
56
Linphone/data/emoji/emojiSvgs/1f979.svg
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Raised-Hand" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 128 128" style="enable-background:new 0 0 128 128;" xml:space="preserve">
|
||||
<radialGradient id="face_1_" cx="63.6" cy="7861.3501" r="56.9597" gradientTransform="matrix(1 0 0 1 0 -7798.4497)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.5" style="stop-color:#FDE030"/>
|
||||
<stop offset="0.92" style="stop-color:#F7C02B"/>
|
||||
<stop offset="1" style="stop-color:#F4A223"/>
|
||||
</radialGradient>
|
||||
<path id="face" style="fill:url(#face_1_);" d="M63.6,118.8c-27.9,0-58-17.5-58-55.9S35.7,7,63.6,7c15.5,0,29.8,5.1,40.4,14.4 c11.5,10.2,17.6,24.6,17.6,41.5s-6.1,31.2-17.6,41.4C93.4,113.6,79,118.8,63.6,118.8z"/>
|
||||
<g id="eyes">
|
||||
<path style="fill:#FFFFFF;" d="M43,47.7c9.58,0.03,17.33,7.82,17.3,17.4s-7.82,17.33-17.4,17.3c-9.58-0.03-17.33-7.82-17.3-17.4 C25.66,55.43,33.43,47.71,43,47.7"/>
|
||||
<circle style="fill:#422B0D;" cx="42.7" cy="62.8" r="15.4"/>
|
||||
|
||||
<ellipse transform="matrix(0.7659 -0.6429 0.6429 0.7659 -32.8541 47.0076)" style="fill:#FFFFFF;" cx="48.13" cy="68.62" rx="2.6" ry="2.4"/>
|
||||
|
||||
<ellipse transform="matrix(0.8022 -0.5971 0.5971 0.8022 -26.9821 34.5782)" style="fill:#FFFFFF;" cx="38.69" cy="58.01" rx="9" ry="7.3"/>
|
||||
<path style="fill:#FFFFFF;" d="M86,47.7c9.58,0.03,17.33,7.82,17.3,17.4c-0.03,9.58-7.82,17.33-17.4,17.3 c-9.58-0.03-17.33-7.82-17.3-17.4C68.66,55.43,76.43,47.71,86,47.7"/>
|
||||
<circle style="fill:#422B0D;" cx="85.7" cy="62.8" r="15.4"/>
|
||||
|
||||
<ellipse transform="matrix(0.7659 -0.6429 0.6429 0.7659 -22.7305 74.6885)" style="fill:#FFFFFF;" cx="91.21" cy="68.56" rx="2.6" ry="2.4"/>
|
||||
|
||||
<ellipse transform="matrix(0.8022 -0.5971 0.5971 0.8022 -18.4797 60.2586)" style="fill:#FFFFFF;" cx="81.7" cy="58.02" rx="9" ry="7.3"/>
|
||||
</g>
|
||||
<path style="fill:none;" d="M43,47.7c-9.58,0.03-17.33,7.82-17.3,17.4c0.03,9.58,7.82,17.33,17.4,17.3 c9.58-0.03,17.33-7.82,17.3-17.4C60.34,55.43,52.57,47.71,43,47.7"/>
|
||||
<circle style="fill:none;" cx="43.3" cy="62.8" r="15.4"/>
|
||||
<ellipse transform="matrix(0.6429 -0.7659 0.7659 0.6429 -39.0221 53.4261)" style="fill:none;" cx="37.79" cy="68.56" rx="2.4" ry="2.6"/>
|
||||
<ellipse transform="matrix(0.5971 -0.8022 0.8022 0.5971 -27.4803 61.3103)" style="fill:none;" cx="47.29" cy="58.01" rx="7.3" ry="9"/>
|
||||
<g>
|
||||
<defs>
|
||||
<path id="SVGID_1_" d="M86,47.7c-9.58,0.03-17.33,7.82-17.3,17.4c0.03,9.58,7.82,17.33,17.4,17.3c9.58-0.03,17.33-7.82,17.3-17.4 C103.34,55.43,95.57,47.71,86,47.7"/>
|
||||
</defs>
|
||||
<clipPath id="SVGID_2_">
|
||||
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
|
||||
</clipPath>
|
||||
<g style="clip-path:url(#SVGID_2_);">
|
||||
<path style="fill:#29B6F6;" d="M102.35,68.47c-2.16-0.36-4.35,0.48-5.71,2.21c-0.97,1.27-2.49,1.99-4.09,1.95h-0.27 c-1.1,0-2.19,0.3-3.13,0.87c-1.48,0.9-3.35,0.9-4.83,0c-0.94-0.57-2.03-0.87-3.13-0.87c-0.24-0.02-0.47-0.02-0.71,0 c-1.71,0.25-3.42-0.43-4.48-1.79c-1.14-1.55-2.95-2.46-4.88-2.45c-3.31,0.08-5.96,2.76-6,6.07c0.03,3.3,2.7,5.97,6,6 c0.28,0,0.57-0.02,0.85-0.06c1.65-0.27,3.31,0.4,4.31,1.74c1.85,2.65,5.46,3.36,8.17,1.61c1.41-0.89,3.2-0.89,4.61,0 c2.63,1.68,6.1,1.07,8-1.4c0.91-1.25,2.38-1.96,3.93-1.9h0.38c3.34-0.05,6.01-2.8,5.96-6.14c-0.04-2.89-2.12-5.34-4.96-5.86 L102.35,68.47z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<defs>
|
||||
<path id="SVGID_3_" d="M43,47.7c-9.58,0.03-17.33,7.82-17.3,17.4c0.03,9.58,7.82,17.33,17.4,17.3c9.58-0.03,17.33-7.82,17.3-17.4 C60.34,55.43,52.57,47.71,43,47.7"/>
|
||||
</defs>
|
||||
<clipPath id="SVGID_4_">
|
||||
<use xlink:href="#SVGID_3_" style="overflow:visible;"/>
|
||||
</clipPath>
|
||||
<g style="clip-path:url(#SVGID_4_);">
|
||||
<path style="fill:#29B6F6;" d="M59.9,68.47c-2.16-0.36-4.35,0.49-5.7,2.21c-0.98,1.27-2.5,1.99-4.1,1.95h-0.26 c-1.1-0.01-2.19,0.29-3.13,0.87c-1.49,0.9-3.35,0.9-4.84,0c-0.94-0.58-2.03-0.88-3.13-0.87c-0.23-0.01-0.47-0.01-0.7,0 c-1.73,0.26-3.47-0.44-4.53-1.83c-1.14-1.55-2.95-2.46-4.87-2.45c-3.31,0.08-5.96,2.76-6,6.07c0,3.31,2.69,6,6,6 c0.02,0,0.03,0,0.05,0c0.28,0,0.56-0.02,0.84-0.06c1.65-0.27,3.32,0.4,4.32,1.74c1.82,2.66,5.42,3.4,8.15,1.68 c1.4-0.89,3.2-0.89,4.6,0c2.63,1.68,6.1,1.07,8-1.4c0.9-1.25,2.38-1.96,3.92-1.9h0.39c3.31,0.28,6.22-2.19,6.5-5.5 S63.22,68.76,59.9,68.47L59.9,68.47z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="eyebrows">
|
||||
<path style="fill:#422B0D;" d="M27.4,39.8c-2.2,0.4-2.3,3.6,0.1,3.7c5.3,0.07,10.42-1.9,14.3-5.5c1.48-1.28,2.73-2.8,3.7-4.5 c0.58-0.83,0.38-1.97-0.45-2.55c-0.83-0.58-1.97-0.38-2.55,0.45l-0.1,0.1C38.48,35.88,33.19,38.81,27.4,39.8z"/>
|
||||
<path style="fill:#422B0D;" d="M84.5,31.4c-0.58-0.83-1.72-1.03-2.55-0.45c-0.83,0.58-1.03,1.72-0.45,2.55 c0.97,1.7,2.22,3.22,3.7,4.5c3.9,3.57,9.01,5.54,14.3,5.5c2.5-0.1,2.3-3.3,0.1-3.7C93.74,38.84,88.41,35.87,84.5,31.4L84.5,31.4"/>
|
||||
</g>
|
||||
<path style="fill:#EB8F00;" d="M111.49,29.67c5.33,8.6,8.11,18.84,8.11,30.23c0,16.9-6.1,31.2-17.6,41.4 c-10.6,9.3-25,14.5-40.4,14.5c-18.06,0-37-7.35-48.18-22.94c10.76,17.66,31,25.94,50.18,25.94c15.4,0,29.8-5.2,40.4-14.5 c11.5-10.2,17.6-24.5,17.6-41.4C121.6,50.16,118.13,38.84,111.49,29.67z"/>
|
||||
<path id="mouth" style="fill:#422B0D;" d="M64,103.2c10.8,0,17.8-7.9,19.7-11.6c0.7-1.4,0.7-2.6,0.1-3.1c-0.64-0.4-1.46-0.4-2.1,0 c-0.32,0.13-0.62,0.3-0.9,0.5c-4.9,3.52-10.77,5.44-16.8,5.5c-6.01-0.08-11.87-1.96-16.8-5.4c-0.28-0.2-0.58-0.37-0.9-0.5 c-0.64-0.4-1.46-0.4-2.1,0c-0.6,0.6-0.6,1.7,0.1,3.1C46.2,95.3,53.2,103.2,64,103.2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -42,6 +42,8 @@ list(APPEND _LINPHONEAPP_SOURCES
|
|||
model/setting/MediastreamerUtils.cpp
|
||||
|
||||
model/sound-player/SoundPlayerModel.cpp
|
||||
|
||||
model/recorder/RecorderModel.cpp
|
||||
|
||||
model/tool/ToolModel.cpp
|
||||
model/tool/VfsUtils.cpp
|
||||
|
|
|
|||
|
|
@ -40,4 +40,4 @@ void CallHistoryModel::removeCallHistory() {
|
|||
mustBeInLinphoneThread(getClassName() + "::removeCallHistory");
|
||||
qInfo() << "Removing call log: " << Utils::coreStringToAppString(callLog->getCallId());
|
||||
CoreModel::getInstance()->getCore()->removeCallLog(callLog);
|
||||
}
|
||||
}
|
||||
|
|
@ -119,6 +119,10 @@ void ChatModel::deleteHistory() {
|
|||
emit historyDeleted();
|
||||
}
|
||||
|
||||
void ChatModel::deleteMessage(std::shared_ptr<linphone::ChatMessage> message) {
|
||||
mMonitor->deleteMessage(message);
|
||||
}
|
||||
|
||||
void ChatModel::leave() {
|
||||
mMonitor->leave();
|
||||
}
|
||||
|
|
@ -128,6 +132,11 @@ void ChatModel::deleteChatRoom() {
|
|||
emit deleted();
|
||||
}
|
||||
|
||||
std::shared_ptr<linphone::ChatMessage>
|
||||
ChatModel::createVoiceRecordingMessage(const std::shared_ptr<linphone::Recorder> &recorder) {
|
||||
return mMonitor->createVoiceRecordingMessage(recorder);
|
||||
}
|
||||
|
||||
std::shared_ptr<linphone::ChatMessage> ChatModel::createTextMessageFromText(QString text) {
|
||||
return mMonitor->createMessageFromUtf8(Utils::appStringToCoreString(text));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,8 +47,11 @@ public:
|
|||
std::list<std::shared_ptr<linphone::ChatMessage>> getHistory() const;
|
||||
QString getIdentifier() const;
|
||||
void deleteHistory();
|
||||
void deleteMessage(std::shared_ptr<linphone::ChatMessage> message);
|
||||
void deleteChatRoom();
|
||||
void leave();
|
||||
std::shared_ptr<linphone::ChatMessage>
|
||||
createVoiceRecordingMessage(const std::shared_ptr<linphone::Recorder> &recorder);
|
||||
std::shared_ptr<linphone::ChatMessage> createTextMessageFromText(QString text);
|
||||
std::shared_ptr<linphone::ChatMessage> createMessage(QString text,
|
||||
QList<std::shared_ptr<ChatMessageContentModel>> filesContent);
|
||||
|
|
|
|||
|
|
@ -102,6 +102,10 @@ void ChatMessageModel::removeReaction() {
|
|||
sendReaction(QString());
|
||||
}
|
||||
|
||||
void ChatMessageModel::send() {
|
||||
mMonitor->send();
|
||||
}
|
||||
|
||||
QString ChatMessageModel::getOwnReaction() const {
|
||||
auto reaction = mMonitor->getOwnReaction();
|
||||
return reaction ? Utils::coreStringToAppString(reaction->getBody()) : QString();
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ public:
|
|||
|
||||
void removeReaction();
|
||||
|
||||
void send();
|
||||
|
||||
linphone::ChatMessage::State getState() const;
|
||||
|
||||
QString getOwnReaction() const;
|
||||
|
|
|
|||
118
Linphone/model/recorder/RecorderModel.cpp
Normal file
118
Linphone/model/recorder/RecorderModel.cpp
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "core/App.hpp"
|
||||
#include "model/core/CoreModel.hpp"
|
||||
#include "model/setting/SettingsModel.hpp"
|
||||
#include "tool/Utils.hpp"
|
||||
|
||||
#include <QFile>
|
||||
#include <QQmlApplicationEngine>
|
||||
|
||||
#include "RecorderModel.hpp"
|
||||
|
||||
DEFINE_ABSTRACT_OBJECT(RecorderModel)
|
||||
|
||||
// =============================================================================
|
||||
|
||||
RecorderModel::RecorderModel(std::shared_ptr<linphone::Recorder> recorder, QObject *parent) : QObject(parent) {
|
||||
mustBeInLinphoneThread(getClassName());
|
||||
mRecorder = recorder;
|
||||
}
|
||||
|
||||
RecorderModel::~RecorderModel() {
|
||||
}
|
||||
|
||||
std::shared_ptr<linphone::Recorder> RecorderModel::getRecorder() {
|
||||
return mRecorder;
|
||||
}
|
||||
|
||||
int RecorderModel::getDuration() const {
|
||||
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
|
||||
return mRecorder->getDuration();
|
||||
}
|
||||
|
||||
float RecorderModel::getCaptureVolume() const {
|
||||
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
|
||||
return mRecorder->getCaptureVolume();
|
||||
}
|
||||
|
||||
linphone::Recorder::State RecorderModel::getState() const {
|
||||
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
|
||||
return mRecorder->getState();
|
||||
}
|
||||
|
||||
QString RecorderModel::getFile() const {
|
||||
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
|
||||
return Utils::coreStringToAppString(mRecorder->getFile());
|
||||
}
|
||||
|
||||
QStringList RecorderModel::splitSavedFilename(const QString &filename) {
|
||||
QStringList fields = filename.split('_');
|
||||
if (fields.size() == 3 && fields[0] == "vocal" && fields[1].split('-').size() == 3 &&
|
||||
fields[2].split('-').size() == 4) {
|
||||
return fields;
|
||||
} else return QStringList(filename);
|
||||
}
|
||||
|
||||
QDateTime RecorderModel::getDateTimeSavedFilename(const QString &filename) {
|
||||
auto fields = splitSavedFilename(filename);
|
||||
if (fields.size() > 1) return QDateTime::fromString(fields[1] + "_" + fields[2], "yyyy-MM-dd_hh-mm-ss-zzz");
|
||||
else return QDateTime();
|
||||
}
|
||||
|
||||
void RecorderModel::start() {
|
||||
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
|
||||
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(SettingsModel::getInstance()->getSavedCallsFolder()).arg(filename), &soFarSoGood);
|
||||
|
||||
if (!soFarSoGood) {
|
||||
qWarning() << QStringLiteral("Unable to create safe file path for: %1.").arg(filename);
|
||||
emit errorChanged(QString("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);
|
||||
emit errorChanged(QString("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 errorChanged(QString("Unable to start recording to : %1.").arg(filename));
|
||||
}
|
||||
emit stateChanged();
|
||||
emit fileChanged();
|
||||
}
|
||||
|
||||
void RecorderModel::pause() {
|
||||
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
|
||||
mRecorder->pause();
|
||||
emit stateChanged();
|
||||
}
|
||||
|
||||
void RecorderModel::stop() {
|
||||
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
|
||||
// if (mRecorder->getState() == linphone::Recorder::State::Running) // Remove these tests when the SDK do them.
|
||||
// mRecorder->pause();
|
||||
// if (mRecorder->getState() == linphone::Recorder::State::Paused) {
|
||||
mRecorder->close();
|
||||
emit stateChanged();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------------------
|
||||
60
Linphone/model/recorder/RecorderModel.hpp
Normal file
60
Linphone/model/recorder/RecorderModel.hpp
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef RECORDER_MODEL_H
|
||||
#define RECORDER_MODEL_H
|
||||
|
||||
#include "tool/AbstractObject.hpp"
|
||||
#include <linphone++/linphone.hh>
|
||||
|
||||
// =============================================================================
|
||||
|
||||
class RecorderModel : public QObject, public AbstractObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
RecorderModel(std::shared_ptr<linphone::Recorder> recorder, QObject *parent = nullptr);
|
||||
virtual ~RecorderModel();
|
||||
|
||||
std::shared_ptr<linphone::Recorder> getRecorder();
|
||||
|
||||
int getDuration() const;
|
||||
float getCaptureVolume() const;
|
||||
linphone::Recorder::State getState() const;
|
||||
QString getFile() const;
|
||||
|
||||
static QStringList
|
||||
splitSavedFilename(const QString &filename); // If doesn't match to generateSavedFilename, return filename
|
||||
static QDateTime getDateTimeSavedFilename(const QString &filename);
|
||||
|
||||
void start();
|
||||
void pause();
|
||||
void stop();
|
||||
|
||||
signals:
|
||||
void stateChanged();
|
||||
void fileChanged();
|
||||
void errorChanged(QString error);
|
||||
|
||||
private:
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
std::shared_ptr<linphone::Recorder> mRecorder;
|
||||
};
|
||||
#endif
|
||||
|
|
@ -428,6 +428,17 @@ void SettingsModel::setVideoEnabled(const bool enabled) {
|
|||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
bool SettingsModel::getAutoDownloadReceivedFiles() const {
|
||||
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
|
||||
return CoreModel::getInstance()->getCore()->getMaxSizeForAutoDownloadIncomingFiles() == 0;
|
||||
}
|
||||
|
||||
void SettingsModel::setAutoDownloadReceivedFiles(bool status) {
|
||||
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
|
||||
CoreModel::getInstance()->getCore()->setMaxSizeForAutoDownloadIncomingFiles(status ? 0 : -1);
|
||||
emit autoDownloadReceivedFilesChanged(status);
|
||||
}
|
||||
|
||||
bool SettingsModel::getEchoCancellationEnabled() const {
|
||||
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
|
||||
return CoreModel::getInstance()->getCore()->echoCancellationEnabled();
|
||||
|
|
@ -558,6 +569,18 @@ QString SettingsModel::getLogsFolder(const shared_ptr<linphone::Config> &config)
|
|||
: Paths::getLogsDirPath();
|
||||
}
|
||||
|
||||
static inline std::string getLegacySavedCallsFolder(const shared_ptr<linphone::Config> &config) {
|
||||
auto path = config->getString(SettingsModel::UiSection, "saved_videos_folder", "");
|
||||
if (path == "") path = Utils::appStringToCoreString(Paths::getCapturesDirPath());
|
||||
return path;
|
||||
}
|
||||
|
||||
QString SettingsModel::getSavedCallsFolder() const {
|
||||
auto path = mConfig->getString(UiSection, "saved_calls_folder", ""); // Avoid to call default function if exist.
|
||||
if (path == "") path = getLegacySavedCallsFolder(mConfig);
|
||||
return QDir::cleanPath(Utils::coreStringToAppString(path)) + QDir::separator();
|
||||
}
|
||||
|
||||
QString SettingsModel::getLogsUploadUrl() const {
|
||||
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
|
||||
auto core = CoreModel::getInstance()->getCore();
|
||||
|
|
|
|||
|
|
@ -61,6 +61,9 @@ public:
|
|||
bool getEchoCancellationEnabled() const;
|
||||
void setEchoCancellationEnabled(bool enabled);
|
||||
|
||||
void setAutoDownloadReceivedFiles(bool enabled);
|
||||
bool getAutoDownloadReceivedFiles() const;
|
||||
|
||||
// Audio. --------------------------------------------------------------------
|
||||
|
||||
bool getIsInCall() const;
|
||||
|
|
@ -137,6 +140,7 @@ public:
|
|||
QString getLogsFolder() const;
|
||||
void setLogsFolder(const QString &folder);
|
||||
static QString getLogsFolder(const std::shared_ptr<linphone::Config> &config);
|
||||
QString getSavedCallsFolder() const;
|
||||
|
||||
QString getLogsUploadUrl() const;
|
||||
void setLogsUploadUrl(const QString &url);
|
||||
|
|
@ -238,6 +242,9 @@ signals:
|
|||
|
||||
void dndChanged(bool value);
|
||||
|
||||
// Messages. --------------------------------------------------------------------
|
||||
void autoDownloadReceivedFilesChanged(bool enabled);
|
||||
|
||||
private:
|
||||
void notifyConfigReady();
|
||||
MediastreamerUtils::SimpleCaptureGraph *mSimpleCaptureGraph = nullptr;
|
||||
|
|
|
|||
|
|
@ -99,7 +99,13 @@ bool SoundPlayerModel::play(QString source) {
|
|||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
void SoundPlayerModel::seek(int offset) {
|
||||
void SoundPlayerModel::seek(QString source, int offset) {
|
||||
if (!open(source)) {
|
||||
qWarning() << QStringLiteral("Unable to open: `%1`").arg(source);
|
||||
//: Unable to open: `%1`
|
||||
emit errorChanged(QString("sound_player_open_error").arg(source));
|
||||
return;
|
||||
}
|
||||
mMonitor->seek(offset);
|
||||
emit positionChanged(mMonitor->getCurrentPosition());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ public:
|
|||
void pause();
|
||||
bool play(QString source);
|
||||
void stop(bool force = false);
|
||||
void seek(int offset);
|
||||
void seek(QString source, int offset);
|
||||
|
||||
int getPosition() const;
|
||||
bool hasVideo() const; // Call it after playing a video because the detection is not outside this scope.
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
#include "core/participant/ParticipantDeviceCore.hpp"
|
||||
#include "core/path/Paths.hpp"
|
||||
#include "core/payload-type/DownloadablePayloadTypeCore.hpp"
|
||||
#include "core/recorder/RecorderGui.hpp"
|
||||
#include "model/object/VariantObject.hpp"
|
||||
#include "model/tool/ToolModel.hpp"
|
||||
#include "tool/providers/AvatarProvider.hpp"
|
||||
|
|
@ -1948,6 +1949,56 @@ QString Utils::getSafeFilePath(const QString &filePath, bool *soFarSoGood) {
|
|||
return QString("");
|
||||
}
|
||||
|
||||
VariantObject *Utils::createVoiceRecordingMessage(RecorderGui *recorderGui, ChatGui *chatGui) {
|
||||
VariantObject *data = new VariantObject("createVoiceRecordingMessage");
|
||||
if (!data) return nullptr;
|
||||
data->makeRequest([recorderCore = recorderGui ? recorderGui->getCore() : nullptr,
|
||||
chatCore = chatGui ? chatGui->getCore() : nullptr]() {
|
||||
if (!recorderCore || !chatCore) return QVariant();
|
||||
auto model = recorderCore->getModel();
|
||||
auto chatModel = chatCore->getModel();
|
||||
if (!model || !chatModel) return QVariant();
|
||||
auto recorder = model->getRecorder();
|
||||
auto linMessage = chatModel->createVoiceRecordingMessage(recorder);
|
||||
if (linMessage) {
|
||||
auto messageCore = ChatMessageCore::create(linMessage);
|
||||
return QVariant::fromValue(new ChatMessageGui(messageCore));
|
||||
}
|
||||
return QVariant();
|
||||
});
|
||||
data->requestValue();
|
||||
return data;
|
||||
}
|
||||
|
||||
void Utils::sendVoiceRecordingMessage(RecorderGui *recorderGui, ChatGui *chatGui) {
|
||||
auto chatModel = chatGui && chatGui->mCore ? chatGui->mCore->getModel() : nullptr;
|
||||
auto recorderModel = recorderGui && recorderGui->mCore ? recorderGui->mCore->getModel() : nullptr;
|
||||
if (!chatModel || !recorderModel) {
|
||||
//: Error with the recorder
|
||||
QString error = !recorderModel ? tr("recorder_error")
|
||||
//: Error in the chat
|
||||
: tr("chat_error");
|
||||
//: Error
|
||||
showInformationPopup(tr("info_popup_error_title"),
|
||||
//: Could not send voice message : %1
|
||||
tr("info_popup_send_voice_message_error_message").arg(error));
|
||||
return;
|
||||
}
|
||||
App::postModelAsync([chatModel, recorderModel] {
|
||||
mustBeInLinphoneThread(sLog().arg(Q_FUNC_INFO));
|
||||
auto chat = chatModel->getMonitor();
|
||||
auto recorder = recorderModel->getRecorder();
|
||||
auto linMessage = chatModel->createVoiceRecordingMessage(recorder);
|
||||
if (linMessage) {
|
||||
linMessage->send();
|
||||
} else
|
||||
//: Error
|
||||
showInformationPopup(tr("info_popup_error_title"),
|
||||
//: Failed to create message from record
|
||||
tr("info_popup_send_voice_message_sending_error_message"));
|
||||
});
|
||||
}
|
||||
|
||||
bool Utils::isVideo(const QString &path) {
|
||||
if (path.isEmpty()) return false;
|
||||
return QMimeDatabase().mimeTypeForFile(path).name().contains("video/");
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ class ConferenceCore;
|
|||
class ParticipantDeviceCore;
|
||||
class DownloadablePayloadTypeCore;
|
||||
class ChatGui;
|
||||
class RecorderGui;
|
||||
|
||||
class Utils : public QObject, public AbstractObject {
|
||||
Q_OBJECT
|
||||
|
|
@ -174,6 +175,9 @@ public:
|
|||
static QDateTime getOffsettedUTC(const QDateTime &date);
|
||||
Q_INVOKABLE static QString toTimeString(QDateTime date, const QString &format = "hh:mm:ss");
|
||||
|
||||
Q_INVOKABLE static VariantObject *createVoiceRecordingMessage(RecorderGui *recorderGui, ChatGui *chatGui);
|
||||
Q_INVOKABLE static void sendVoiceRecordingMessage(RecorderGui *recorderGui, ChatGui *chatGui);
|
||||
|
||||
// QDir findDirectoryByName(QString startPath, QString name);
|
||||
|
||||
static QString getApplicationProduct();
|
||||
|
|
|
|||
|
|
@ -149,6 +149,7 @@ list(APPEND _LINPHONEAPP_QML_FILES
|
|||
view/Page/Layout/Settings/DebugSettingsLayout.qml
|
||||
view/Page/Layout/Settings/LdapSettingsLayout.qml
|
||||
view/Page/Layout/Settings/CarddavSettingsLayout.qml
|
||||
view/Page/Layout/Settings/ChatSettingsLayout.qml
|
||||
view/Page/Layout/Settings/SecuritySettingsLayout.qml
|
||||
view/Page/Layout/Settings/NetworkSettingsLayout.qml
|
||||
view/Page/Layout/Settings/AdvancedSettingsLayout.qml
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ Control.Button {
|
|||
property color pressedTextColor: style?.text?.pressed || Qt.darker(textColor, 1.1)
|
||||
property color borderColor: style?.borderColor || "transparent"
|
||||
ToolTip.visible: hovered && ToolTip.text != ""
|
||||
ToolTip.delay: 1000
|
||||
ToolTip.delay: 500
|
||||
property color disabledFilterColor: color.hslLightness > 0.5
|
||||
? DefaultStyle.grey_0
|
||||
: DefaultStyle.grey_400
|
||||
|
|
@ -199,9 +199,20 @@ Control.Button {
|
|||
}
|
||||
Component{
|
||||
id: imageComponent
|
||||
ButtonImage{
|
||||
Item {
|
||||
width: stacklayout.width
|
||||
height: stacklayout.height
|
||||
ButtonImage {
|
||||
id: buttonIcon
|
||||
anchors.fill: parent
|
||||
}
|
||||
ButtonImage {
|
||||
z: buttonIcon.z + 1
|
||||
visible: !mainItem.enabled
|
||||
anchors.fill: parent
|
||||
colorizationColor: DefaultStyle.grey_0
|
||||
opacity: 0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
Component{
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ Button {
|
|||
// bottomPadding: Math.round(16 * DefaultStyle.dp)
|
||||
// leftPadding: Math.round(16 * DefaultStyle.dp)
|
||||
// rightPadding: Math.round(16 * DefaultStyle.dp)
|
||||
icon.width: width
|
||||
icon.height: width
|
||||
// icon.width: width
|
||||
// icon.height: width
|
||||
radius: width * 2
|
||||
// width: Math.round(24 * DefaultStyle.dp)
|
||||
height: width
|
||||
|
|
|
|||
|
|
@ -7,41 +7,45 @@ import UtilsCpp
|
|||
|
||||
// =============================================================================
|
||||
|
||||
Loader{
|
||||
Item {
|
||||
id: mainItem
|
||||
property ChatMessageContentGui chatMessageContentGui
|
||||
property int availableWidth : parent.width
|
||||
|
||||
// property string filePath : tempFile.filePath
|
||||
|
||||
active: chatMessageContentGui && chatMessageContentGui.core.isVoiceRecording
|
||||
|
||||
// onChatMessageContentGuiChanged: if(chatMessageContentGui){
|
||||
// tempFile.createFileFromContentModel(chatMessageContentGui, false);
|
||||
// }
|
||||
|
||||
// TemporaryFile {
|
||||
// id: tempFile
|
||||
// }
|
||||
|
||||
sourceComponent: Item {
|
||||
id: loadedItem
|
||||
property bool isPlaying : soundPlayerGui && soundPlayerGui.core.playbackState === LinphoneEnums.PlaybackState.PlayingState
|
||||
onIsPlayingChanged: isPlaying ? mediaProgressBar.resume() : mediaProgressBar.stop()
|
||||
|
||||
width: mainItem.width
|
||||
height: mainItem.height
|
||||
|
||||
clip: false
|
||||
property var chatMessageObj
|
||||
property ChatMessageGui chatMessage: chatMessageObj && chatMessageObj.value || null
|
||||
property bool isPlaying : soudPlayerLoader.item && soudPlayerLoader.item.core.playbackState === LinphoneEnums.PlaybackState.PlayingState
|
||||
onIsPlayingChanged: isPlaying ? mediaProgressBar.resume() : mediaProgressBar.stop()
|
||||
property bool recording: false
|
||||
property RecorderGui recorderGui: recorderLoader.item || null
|
||||
|
||||
SoundPlayerGui {
|
||||
signal voiceRecordingMessageCreationRequested(RecorderGui recorderGui)
|
||||
signal stopRecording()
|
||||
|
||||
function createVoiceMessageInChat(chat) {
|
||||
if (recorderLoader.item) {
|
||||
mainItem.chatMessageObj = UtilsCpp.createVoiceRecordingMessage(recorderLoader.item, chat)
|
||||
} else {
|
||||
//: Error
|
||||
UtilsCpp.showInformationPopup(qsTr("information_popup_error_title"),
|
||||
//: Failed to create voice message : error in recorder
|
||||
qsTr("information_popup_voice_message_error_message"), false)
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: soudPlayerLoader
|
||||
property int duration: mainItem.chatMessageContentGui
|
||||
? mainItem.chatMessageContentGui.core.fileDuration
|
||||
: item
|
||||
? item.core.duration
|
||||
: 0
|
||||
property int position: item?.core.position || 0
|
||||
active: mainItem.chatMessageContentGui && mainItem.chatMessageContentGui.core.isVoiceRecording
|
||||
sourceComponent: SoundPlayerGui {
|
||||
id: soundPlayerGui
|
||||
property int duration: mainItem.chatMessageContentGui ? mainItem.chatMessageContentGui.core.fileDuration : core.duration
|
||||
property int position: core.position
|
||||
source: mainItem.chatMessageContentGui && mainItem.chatMessageContentGui.core.filePath
|
||||
|
||||
function play(){
|
||||
if(loadedItem.isPlaying){// Pause the play
|
||||
if(mainItem.isPlaying){// Pause the play
|
||||
soundPlayerGui.core.lPause()
|
||||
}else{// Play the audio
|
||||
soundPlayerGui.core.lPlay()
|
||||
|
|
@ -51,41 +55,82 @@ Loader{
|
|||
mediaProgressBar.value = 101
|
||||
}
|
||||
onPositionChanged: {
|
||||
mediaProgressBar.progressPosition = position
|
||||
mediaProgressBar.value = 100 * ( mediaProgressBar.progressPosition / duration)
|
||||
mediaProgressBar.progressPosition = soudPlayerLoader.position
|
||||
mediaProgressBar.value = 100 * ( mediaProgressBar.progressPosition / soudPlayerLoader.duration)
|
||||
}
|
||||
onSourceChanged: if (source != "") {
|
||||
// core.lPlay()// This will open the file and allow seeking
|
||||
// core.lPause()
|
||||
core.lOpen()
|
||||
core.lOpen() // Open the file and allow seeking
|
||||
mediaProgressBar.value = 0
|
||||
mediaProgressBar.refresh()
|
||||
}
|
||||
onErrorChanged: (error) => {
|
||||
//: Error
|
||||
UtilsCpp.showInformationPopup(qsTr("information_popup_error_title"), error, false)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: recorderLoader
|
||||
active: mainItem.recording && !mainItem.chatMessageContentGui
|
||||
property int duration: item?.core.duration || 0
|
||||
property int captureVolume: item?.core.captureVolume || 0
|
||||
property var state: item?.core.state
|
||||
|
||||
MediaProgressBar{
|
||||
id: mediaProgressBar
|
||||
anchors.fill: parent
|
||||
progressDuration: soundPlayerGui ? soundPlayerGui.duration : chatMessageContentGui.core.fileDuration
|
||||
progressPosition: 0
|
||||
value: 0
|
||||
function refresh(){
|
||||
if(soundPlayerGui){
|
||||
soundPlayerGui.core.lRefreshPosition()
|
||||
}
|
||||
Connections {
|
||||
target: mainItem
|
||||
function onStopRecording() {
|
||||
recorderLoader.item.core.lStop()
|
||||
}
|
||||
onEndReached:{
|
||||
if(soundPlayerGui)
|
||||
soundPlayerGui.core.lStop()
|
||||
}
|
||||
onPlayStopButtonToggled: soundPlayerGui.play()
|
||||
onRefreshPositionRequested: refresh()
|
||||
onSeekRequested: (ms) => {
|
||||
if(soundPlayerGui) {
|
||||
soundPlayerGui.core.lSeek(ms)
|
||||
}
|
||||
|
||||
sourceComponent: RecorderGui {
|
||||
id: recorderGui
|
||||
onReady: core.lStart()
|
||||
onStateChanged: (state) => {
|
||||
if (state === LinphoneEnums.RecorderState.Running) mediaProgressBar.start()
|
||||
if (state === LinphoneEnums.RecorderState.Closed) {
|
||||
mediaProgressBar.stop()
|
||||
mainItem.voiceRecordingMessageCreationRequested(recorderGui)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MediaProgressBar{
|
||||
id: mediaProgressBar
|
||||
anchors.fill: parent
|
||||
progressDuration: soudPlayerLoader.active
|
||||
? soudPlayerLoader.duration
|
||||
: recorderLoader
|
||||
? recorderLoader.duration
|
||||
: chatMessageContentGui.core.fileDuration
|
||||
progressPosition: 0
|
||||
value: 0
|
||||
recording: recorderLoader.state === LinphoneEnums.RecorderState.Running
|
||||
function refresh(){
|
||||
if(soudPlayerLoader.item){
|
||||
soudPlayerLoader.item.core.lRefreshPosition()
|
||||
} else if (recorderLoader.item) {
|
||||
recorderLoader.item.core.lRefresh()
|
||||
}
|
||||
}
|
||||
onEndReached:{
|
||||
if(soudPlayerLoader.item)
|
||||
soudPlayerLoader.item.core.lStop()
|
||||
}
|
||||
onPlayStopButtonToggled: {
|
||||
if(soudPlayerLoader.item) {
|
||||
soudPlayerLoader.item.play()
|
||||
} else if (recorderLoader.item) {
|
||||
recorderLoader.item.core.lStop()
|
||||
}
|
||||
}
|
||||
onRefreshPositionRequested: refresh()
|
||||
onSeekRequested: (ms) => {
|
||||
if(soudPlayerLoader.active) {
|
||||
soudPlayerLoader.item.core.lSeek(ms)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -342,9 +342,9 @@ ListView {
|
|||
spacing: Math.round(10 * DefaultStyle.dp)
|
||||
Layout.fillWidth: true
|
||||
onClicked: {
|
||||
//: Delete the chat ?
|
||||
//: Delete the conversation ?
|
||||
mainWindow.showConfirmationLambdaPopup(qsTr("chat_list_delete_chat_popup_title"),
|
||||
//: This chat and all its messages will be deleted. Do You want to continue ?
|
||||
//: This conversation and all its messages will be deleted. Do You want to continue ?
|
||||
qsTr("chat_list_delete_chat_popup_message"),
|
||||
"",
|
||||
function(confirmed) {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ ColumnLayout {
|
|||
// VOICE MESSAGES
|
||||
Repeater {
|
||||
id: messagesVoicesList
|
||||
visible: mainItem.chatMessageGui.core.isVoiceRecording && count > 0
|
||||
visible: count > 0
|
||||
model: ChatMessageContentProxy{
|
||||
filterType: ChatMessageContentProxy.FilterContentType.Voice
|
||||
chatMessageGui: mainItem.chatMessageGui
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ ProgressBar {
|
|||
animationTest.start()
|
||||
}
|
||||
function resume(){
|
||||
if(mainItem.value >= 100)
|
||||
if (mainItem.value >= 100)
|
||||
mainItem.value = 0
|
||||
animationTest.start()
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ ProgressBar {
|
|||
signal endReached()
|
||||
signal refreshPositionRequested()
|
||||
signal seekRequested(int ms)
|
||||
Timer{
|
||||
Timer {
|
||||
id: animationTest
|
||||
repeat: true
|
||||
onTriggered: mainItem.refreshPositionRequested()
|
||||
|
|
@ -60,7 +60,6 @@ ProgressBar {
|
|||
mainItem.value = 100// Stay at 100
|
||||
progressPosition = progressDuration
|
||||
}
|
||||
console.log("end reached")
|
||||
mainItem.endReached()
|
||||
}
|
||||
}
|
||||
|
|
@ -128,8 +127,7 @@ ProgressBar {
|
|||
onClicked: {
|
||||
mainItem.playStopButtonToggled()
|
||||
}
|
||||
borderColor: "transparent"
|
||||
style: ButtonStyle.secondary
|
||||
style: ButtonStyle.player
|
||||
}
|
||||
Control.Control {
|
||||
anchors.right: parent.right
|
||||
|
|
@ -150,6 +148,8 @@ ProgressBar {
|
|||
visible: mainItem.recording
|
||||
colorizationColor: DefaultStyle.danger_500main
|
||||
imageSource: AppIcons.recordFill
|
||||
Layout.preferredWidth: Math.round(14 * DefaultStyle.dp)
|
||||
Layout.preferredHeight: Math.round(14 * DefaultStyle.dp)
|
||||
}
|
||||
Text {
|
||||
id: durationText
|
||||
|
|
|
|||
|
|
@ -11,26 +11,27 @@ import 'qrc:/qt/qml/Linphone/view/Control/Tool/Helper/utils.js' as Utils
|
|||
Control.Control {
|
||||
id: mainItem
|
||||
|
||||
property alias placeholderText: sendingTextArea.placeholderText
|
||||
property alias text: sendingTextArea.text
|
||||
property alias textArea: sendingTextArea
|
||||
property alias cursorPosition: sendingTextArea.cursorPosition
|
||||
property alias emojiPickerButtonChecked: emojiPickerButton.checked
|
||||
// property alias placeholderText: sendingTextArea.placeholderText
|
||||
property string text
|
||||
property var textArea
|
||||
// property alias cursorPosition: sendingTextArea.cursorPosition
|
||||
property bool emojiPickerButtonChecked
|
||||
|
||||
property bool dropEnabled: true
|
||||
property string dropDisabledReason
|
||||
property bool isEphemeral : false
|
||||
property bool emojiVisible: false
|
||||
|
||||
property ChatGui chat
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
signal dropped (var files)
|
||||
signal validText (string text)
|
||||
signal sendText()
|
||||
signal audioRecordRequest()
|
||||
signal sendMessage()
|
||||
signal emojiClicked()
|
||||
signal composing()
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function _emitFiles (files) {
|
||||
|
|
@ -63,118 +64,196 @@ Control.Control {
|
|||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
color: DefaultStyle.grey_100
|
||||
MediumButton {
|
||||
id: expandButton
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Math.round(4 * DefaultStyle.dp)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
style: ButtonStyle.noBackgroundOrange
|
||||
icon.source: checked ? AppIcons.downArrow : AppIcons.upArrow
|
||||
checkable: true
|
||||
}
|
||||
}
|
||||
contentItem: RowLayout {
|
||||
spacing: Math.round(20 * DefaultStyle.dp)
|
||||
RowLayout {
|
||||
spacing: Math.round(16 * DefaultStyle.dp)
|
||||
BigButton {
|
||||
id: emojiPickerButton
|
||||
style: ButtonStyle.noBackground
|
||||
checkable: true
|
||||
icon.source: checked ? AppIcons.closeX : AppIcons.smiley
|
||||
}
|
||||
BigButton {
|
||||
style: ButtonStyle.noBackground
|
||||
icon.source: AppIcons.paperclip
|
||||
onClicked: {
|
||||
fileDialog.open()
|
||||
contentItem: Control.StackView {
|
||||
id: sendingAreaStackView
|
||||
initialItem: textAreaComp
|
||||
Component {
|
||||
id: textAreaComp
|
||||
RowLayout {
|
||||
// disable record button if call ongoing
|
||||
CallProxy {
|
||||
id: callsModel
|
||||
sourceModel: AppCpp.calls
|
||||
}
|
||||
}
|
||||
Control.Control {
|
||||
Layout.fillWidth: true
|
||||
leftPadding: Math.round(15 * DefaultStyle.dp)
|
||||
rightPadding: Math.round(15 * DefaultStyle.dp)
|
||||
topPadding: Math.round(15 * DefaultStyle.dp)
|
||||
bottomPadding: Math.round(15 * DefaultStyle.dp)
|
||||
background: Rectangle {
|
||||
id: inputBackground
|
||||
anchors.fill: parent
|
||||
radius: Math.round(35 * DefaultStyle.dp)
|
||||
color: DefaultStyle.grey_0
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onPressed: sendingTextArea.forceActiveFocus()
|
||||
cursorShape: Qt.IBeamCursor
|
||||
spacing: Math.round(16 * DefaultStyle.dp)
|
||||
BigButton {
|
||||
id: emojiPickerButton
|
||||
style: ButtonStyle.noBackground
|
||||
checkable: true
|
||||
icon.source: checked ? AppIcons.closeX : AppIcons.smiley
|
||||
onCheckedChanged: mainItem.emojiPickerButtonChecked = checked
|
||||
Connections {
|
||||
target: mainItem
|
||||
function onEmojiPickerButtonCheckedChanged() {
|
||||
emojiPickerButton.checked = mainItem.emojiPickerButtonChecked
|
||||
}
|
||||
}
|
||||
}
|
||||
contentItem: RowLayout {
|
||||
Flickable {
|
||||
id: sendingAreaFlickable
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.min(Math.round(60 * DefaultStyle.dp), contentHeight)
|
||||
Binding {
|
||||
target: sendingAreaFlickable
|
||||
when: expandButton.checked
|
||||
property: "Layout.preferredHeight"
|
||||
value: Math.round(250 * DefaultStyle.dp)
|
||||
restoreMode: Binding.RestoreBindingOrValue
|
||||
BigButton {
|
||||
style: ButtonStyle.noBackground
|
||||
icon.source: AppIcons.paperclip
|
||||
onClicked: {
|
||||
fileDialog.open()
|
||||
}
|
||||
}
|
||||
Control.Control {
|
||||
Layout.fillWidth: true
|
||||
leftPadding: Math.round(15 * DefaultStyle.dp)
|
||||
rightPadding: Math.round(15 * DefaultStyle.dp)
|
||||
topPadding: Math.round(15 * DefaultStyle.dp)
|
||||
bottomPadding: Math.round(15 * DefaultStyle.dp)
|
||||
background: Rectangle {
|
||||
id: inputBackground
|
||||
anchors.fill: parent
|
||||
radius: Math.round(35 * DefaultStyle.dp)
|
||||
color: DefaultStyle.grey_0
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onPressed: sendingTextArea.forceActiveFocus()
|
||||
cursorShape: Qt.IBeamCursor
|
||||
}
|
||||
Layout.fillHeight: true
|
||||
contentHeight: sendingTextArea.contentHeight
|
||||
contentWidth: width
|
||||
}
|
||||
contentItem: RowLayout {
|
||||
Flickable {
|
||||
id: sendingAreaFlickable
|
||||
Layout.preferredHeight: Math.min(Math.round(60 * DefaultStyle.dp), contentHeight)
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
contentHeight: sendingTextArea.contentHeight
|
||||
contentWidth: width
|
||||
|
||||
function ensureVisible(r) {
|
||||
if (contentX >= r.x)
|
||||
contentX = r.x;
|
||||
else if (contentX+width <= r.x+r.width)
|
||||
contentX = r.x+r.width-width;
|
||||
if (contentY >= r.y)
|
||||
contentY = r.y;
|
||||
else if (contentY+height <= r.y+r.height)
|
||||
contentY = r.y+r.height-height;
|
||||
}
|
||||
|
||||
TextArea {
|
||||
id: sendingTextArea
|
||||
width: sendingAreaFlickable.width
|
||||
height: sendingAreaFlickable.height
|
||||
textFormat: TextEdit.AutoText
|
||||
//: Say something… : placeholder text for sending message text area
|
||||
placeholderText: qsTr("chat_view_send_area_placeholder_text")
|
||||
placeholderTextColor: DefaultStyle.main2_400
|
||||
color: DefaultStyle.main2_700
|
||||
font {
|
||||
pixelSize: Typography.p1.pixelSize
|
||||
weight: Typography.p1.weight
|
||||
function ensureVisible(r) {
|
||||
if (contentX >= r.x)
|
||||
contentX = r.x;
|
||||
else if (contentX+width <= r.x+r.width)
|
||||
contentX = r.x+r.width-width;
|
||||
if (contentY >= r.y)
|
||||
contentY = r.y;
|
||||
else if (contentY+height <= r.y+r.height)
|
||||
contentY = r.y+r.height-height;
|
||||
}
|
||||
onCursorRectangleChanged: sendingAreaFlickable.ensureVisible(cursorRectangle)
|
||||
wrapMode: TextEdit.WordWrap
|
||||
Keys.onPressed: (event) => {
|
||||
if ((event.key == Qt.Key_Enter || event.key == Qt.Key_Return))
|
||||
if(!(event.modifiers & Qt.ShiftModifier)) {
|
||||
mainItem.sendText()
|
||||
event.accepted = true
|
||||
|
||||
TextArea {
|
||||
id: sendingTextArea
|
||||
width: sendingAreaFlickable.width
|
||||
height: sendingAreaFlickable.height
|
||||
textFormat: TextEdit.AutoText
|
||||
onTextChanged: mainItem.text = text
|
||||
Component.onCompleted: mainItem.textArea = sendingTextArea
|
||||
//: Say something… : placeholder text for sending message text area
|
||||
placeholderText: qsTr("chat_view_send_area_placeholder_text")
|
||||
placeholderTextColor: DefaultStyle.main2_400
|
||||
color: DefaultStyle.main2_700
|
||||
font {
|
||||
pixelSize: Typography.p1.pixelSize
|
||||
weight: Typography.p1.weight
|
||||
}
|
||||
onCursorRectangleChanged: sendingAreaFlickable.ensureVisible(cursorRectangle)
|
||||
wrapMode: TextEdit.WordWrap
|
||||
Keys.onPressed: (event) => {
|
||||
if ((event.key == Qt.Key_Enter || event.key == Qt.Key_Return))
|
||||
if(!(event.modifiers & Qt.ShiftModifier)) {
|
||||
mainItem.sendMessage()
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: mainItem
|
||||
function onTextChanged() {
|
||||
if (mainItem.text !== text) text = mainItem.text
|
||||
}
|
||||
function onSendMessage() {
|
||||
sendingTextArea.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
id: stackButton
|
||||
spacing: 0
|
||||
BigButton {
|
||||
id: recordButton
|
||||
enabled: !callsModel.currentCall
|
||||
ToolTip.visible: !enabled && hovered
|
||||
//: Cannot record a message while a call is ongoing
|
||||
ToolTip.text: qsTr("cannot_record_while_in_call_tooltip")
|
||||
visible: sendingTextArea.text.length === 0
|
||||
style: ButtonStyle.noBackground
|
||||
hoverEnabled: true
|
||||
icon.source: AppIcons.microphone
|
||||
onClicked: {
|
||||
sendingAreaStackView.push(voiceMessageRecordComp)
|
||||
}
|
||||
}
|
||||
BigButton {
|
||||
visible: sendingTextArea.text.length !== 0
|
||||
style: ButtonStyle.noBackgroundOrange
|
||||
icon.source: AppIcons.paperPlaneRight
|
||||
onClicked: {
|
||||
mainItem.sendMessage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
id: stackButton
|
||||
spacing: 0
|
||||
BigButton {
|
||||
visible: sendingTextArea.text.length === 0
|
||||
style: ButtonStyle.noBackground
|
||||
icon.source: AppIcons.microphone
|
||||
onClicked: {
|
||||
console.log("TODO : go to record message")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: voiceMessageRecordComp
|
||||
RowLayout {
|
||||
spacing: Math.round(16 * DefaultStyle.dp)
|
||||
RoundButton {
|
||||
style: ButtonStyle.player
|
||||
shadowEnabled: true
|
||||
padding: Math.round(4 * DefaultStyle.dp)
|
||||
icon.width: Math.round(22 * DefaultStyle.dp)
|
||||
icon.height: Math.round(22 * DefaultStyle.dp)
|
||||
icon.source: AppIcons.closeX
|
||||
width: Math.round(30 * DefaultStyle.dp)
|
||||
Layout.preferredWidth: width
|
||||
Layout.preferredHeight: height
|
||||
onClicked: {
|
||||
if (voiceMessage.chatMessage) mainItem.chat.core.lDeleteMessage(voiceMessage.chatMessage)
|
||||
sendingAreaStackView.pop()
|
||||
}
|
||||
}
|
||||
ChatAudioContent {
|
||||
id: voiceMessage
|
||||
recording: true
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.round(48 * DefaultStyle.dp)
|
||||
chatMessageContentGui: chatMessage ? chatMessage.core.getVoiceRecordingContent() : null
|
||||
onVoiceRecordingMessageCreationRequested: (recorderGui) => {
|
||||
chatMessageObj = UtilsCpp.createVoiceRecordingMessage(recorderGui, mainItem.chat)
|
||||
}
|
||||
}
|
||||
BigButton {
|
||||
id: sendButton
|
||||
style: ButtonStyle.noBackgroundOrange
|
||||
icon.source: AppIcons.paperPlaneRight
|
||||
icon.width: Math.round(22 * DefaultStyle.dp)
|
||||
icon.height: Math.round(22 * DefaultStyle.dp)
|
||||
// Layout.preferredWidth: icon.width
|
||||
// Layout.preferredHeight: icon.height
|
||||
property bool sendVoiceRecordingOnCreated: false
|
||||
onClicked: {
|
||||
if (voiceMessage.chatMessage) {
|
||||
voiceMessage.chatMessage.core.lSend()
|
||||
sendingAreaStackView.pop()
|
||||
}
|
||||
BigButton {
|
||||
visible: sendingTextArea.text.length !== 0
|
||||
style: ButtonStyle.noBackgroundOrange
|
||||
icon.source: AppIcons.paperPlaneRight
|
||||
onClicked: {
|
||||
mainItem.sendText()
|
||||
else {
|
||||
sendVoiceRecordingOnCreated = true
|
||||
voiceMessage.stopRecording()
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: voiceMessage
|
||||
function onChatMessageChanged() {
|
||||
if (sendButton.sendVoiceRecordingOnCreated) {
|
||||
voiceMessage.chatMessage.core.lSend()
|
||||
sendButton.sendVoiceRecordingOnCreated = false
|
||||
sendingAreaStackView.pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ RowLayout {
|
|||
}
|
||||
|
||||
onGroupCall: {
|
||||
mainWindow.showConfirmationLambdaPopup(qsTr(""),
|
||||
mainWindow.showConfirmationLambdaPopup("",
|
||||
qsTr("chat_view_group_call_toast_message"),
|
||||
"",
|
||||
function(confirmed) {
|
||||
|
|
@ -115,153 +115,163 @@ RowLayout {
|
|||
}
|
||||
]
|
||||
|
||||
content: ColumnLayout {
|
||||
spacing: 0
|
||||
content: Control.SplitView {
|
||||
anchors.fill: parent
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
ChatMessagesListView {
|
||||
id: chatMessagesListView
|
||||
clip: true
|
||||
height: contentHeight
|
||||
backgroundColor: splitPanel.panelColor
|
||||
width: parent.width - anchors.leftMargin - anchors.rightMargin
|
||||
chat: mainItem.chat
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Math.round(18 * DefaultStyle.dp)
|
||||
anchors.rightMargin: Math.round(18 * DefaultStyle.dp)
|
||||
Control.ScrollBar.vertical: scrollbar
|
||||
|
||||
Popup {
|
||||
id: emojiPickerPopup
|
||||
y: Math.round(chatMessagesListView.y + chatMessagesListView.height - height - 8*DefaultStyle.dp)
|
||||
x: Math.round(chatMessagesListView.x + 8*DefaultStyle.dp)
|
||||
width: Math.round(393 * DefaultStyle.dp)
|
||||
height: Math.round(291 * DefaultStyle.dp)
|
||||
visible: messageSender.emojiPickerButtonChecked
|
||||
closePolicy: Popup.CloseOnPressOutside
|
||||
onClosed: messageSender.emojiPickerButtonChecked = false
|
||||
padding: 10 * DefaultStyle.dp
|
||||
background: Item {
|
||||
anchors.fill: parent
|
||||
Rectangle {
|
||||
id: buttonBackground
|
||||
anchors.fill: parent
|
||||
color: DefaultStyle.grey_0
|
||||
radius: Math.round(20 * DefaultStyle.dp)
|
||||
}
|
||||
MultiEffect {
|
||||
anchors.fill: buttonBackground
|
||||
source: buttonBackground
|
||||
shadowEnabled: true
|
||||
shadowColor: DefaultStyle.grey_1000
|
||||
shadowBlur: 0.1
|
||||
shadowOpacity: 0.5
|
||||
}
|
||||
}
|
||||
contentItem: EmojiPicker {
|
||||
id: emojiPicker
|
||||
editor: messageSender.textArea
|
||||
}
|
||||
}
|
||||
}
|
||||
ScrollBar {
|
||||
id: scrollbar
|
||||
visible: chatMessagesListView.contentHeight > parent.height
|
||||
active: visible
|
||||
anchors.top: chatMessagesListView.top
|
||||
anchors.bottom: chatMessagesListView.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Math.round(5 * DefaultStyle.dp)
|
||||
policy: Control.ScrollBar.AsNeeded
|
||||
}
|
||||
orientation: Qt.Vertical
|
||||
handle: Rectangle {
|
||||
implicitHeight: Math.round(8 * DefaultStyle.dp)
|
||||
color: Control.SplitHandle.hovered ? DefaultStyle.grey_200 : DefaultStyle.grey_100
|
||||
}
|
||||
Control.Control {
|
||||
id: selectedFilesArea
|
||||
visible: selectedFiles.count > 0
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.round(104 * DefaultStyle.dp)
|
||||
topPadding: Math.round(12 * DefaultStyle.dp)
|
||||
bottomPadding: Math.round(12 * DefaultStyle.dp)
|
||||
leftPadding: Math.round(19 * DefaultStyle.dp)
|
||||
rightPadding: Math.round(19 * DefaultStyle.dp)
|
||||
|
||||
Button {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: selectedFilesArea.topPadding
|
||||
anchors.rightMargin: selectedFilesArea.rightPadding
|
||||
icon.source: AppIcons.closeX
|
||||
style: ButtonStyle.noBackground
|
||||
onClicked: {
|
||||
contents.clear()
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
Control.SplitView.fillHeight: true
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
ChatMessagesListView {
|
||||
id: chatMessagesListView
|
||||
clip: true
|
||||
height: contentHeight
|
||||
backgroundColor: splitPanel.panelColor
|
||||
width: parent.width - anchors.leftMargin - anchors.rightMargin
|
||||
chat: mainItem.chat
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Math.round(18 * DefaultStyle.dp)
|
||||
anchors.rightMargin: Math.round(18 * DefaultStyle.dp)
|
||||
Control.ScrollBar.vertical: scrollbar
|
||||
|
||||
Popup {
|
||||
id: emojiPickerPopup
|
||||
y: Math.round(chatMessagesListView.y + chatMessagesListView.height - height - 8*DefaultStyle.dp)
|
||||
x: Math.round(chatMessagesListView.x + 8*DefaultStyle.dp)
|
||||
width: Math.round(393 * DefaultStyle.dp)
|
||||
height: Math.round(291 * DefaultStyle.dp)
|
||||
visible: messageSender.emojiPickerButtonChecked
|
||||
closePolicy: Popup.CloseOnPressOutside
|
||||
onClosed: messageSender.emojiPickerButtonChecked = false
|
||||
padding: 10 * DefaultStyle.dp
|
||||
background: Item {
|
||||
anchors.fill: parent
|
||||
Rectangle {
|
||||
id: buttonBackground
|
||||
anchors.fill: parent
|
||||
color: DefaultStyle.grey_0
|
||||
radius: Math.round(20 * DefaultStyle.dp)
|
||||
}
|
||||
MultiEffect {
|
||||
anchors.fill: buttonBackground
|
||||
source: buttonBackground
|
||||
shadowEnabled: true
|
||||
shadowColor: DefaultStyle.grey_1000
|
||||
shadowBlur: 0.1
|
||||
shadowOpacity: 0.5
|
||||
}
|
||||
}
|
||||
contentItem: EmojiPicker {
|
||||
id: emojiPicker
|
||||
editor: messageSender.textArea
|
||||
}
|
||||
}
|
||||
}
|
||||
ScrollBar {
|
||||
id: scrollbar
|
||||
visible: chatMessagesListView.contentHeight > parent.height
|
||||
active: visible
|
||||
anchors.top: chatMessagesListView.top
|
||||
anchors.bottom: chatMessagesListView.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Math.round(5 * DefaultStyle.dp)
|
||||
policy: Control.ScrollBar.AsNeeded
|
||||
}
|
||||
}
|
||||
background: Item{
|
||||
anchors.fill: parent
|
||||
Rectangle {
|
||||
color: DefaultStyle.grey_0
|
||||
border.color: DefaultStyle.main2_100
|
||||
border.width: Math.round(2 * DefaultStyle.dp)
|
||||
radius: Math.round(20 * DefaultStyle.dp)
|
||||
height: parent.height / 2
|
||||
Control.Control {
|
||||
id: selectedFilesArea
|
||||
visible: selectedFiles.count > 0
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.round(104 * DefaultStyle.dp)
|
||||
topPadding: Math.round(12 * DefaultStyle.dp)
|
||||
bottomPadding: Math.round(12 * DefaultStyle.dp)
|
||||
leftPadding: Math.round(19 * DefaultStyle.dp)
|
||||
rightPadding: Math.round(19 * DefaultStyle.dp)
|
||||
|
||||
Button {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
}
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 2 * parent.height / 3
|
||||
}
|
||||
}
|
||||
contentItem: ListView {
|
||||
id: selectedFiles
|
||||
orientation: ListView.Horizontal
|
||||
spacing: Math.round(16 * DefaultStyle.dp)
|
||||
model: ChatMessageContentProxy {
|
||||
id: contents
|
||||
filterType: ChatMessageContentProxy.FilterContentType.File
|
||||
}
|
||||
delegate: Item {
|
||||
width: Math.round(80 * DefaultStyle.dp)
|
||||
height: Math.round(80 * DefaultStyle.dp)
|
||||
FileView {
|
||||
contentGui: modelData
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
width: Math.round(69 * DefaultStyle.dp)
|
||||
height: Math.round(69 * DefaultStyle.dp)
|
||||
anchors.topMargin: selectedFilesArea.topPadding
|
||||
anchors.rightMargin: selectedFilesArea.rightPadding
|
||||
icon.source: AppIcons.closeX
|
||||
style: ButtonStyle.noBackground
|
||||
onClicked: {
|
||||
contents.clear()
|
||||
}
|
||||
RoundButton {
|
||||
icon.source: AppIcons.closeX
|
||||
icon.width: Math.round(12 * DefaultStyle.dp)
|
||||
icon.height: Math.round(12 * DefaultStyle.dp)
|
||||
}
|
||||
background: Item{
|
||||
anchors.fill: parent
|
||||
Rectangle {
|
||||
color: DefaultStyle.grey_0
|
||||
border.color: DefaultStyle.main2_100
|
||||
border.width: Math.round(2 * DefaultStyle.dp)
|
||||
radius: Math.round(20 * DefaultStyle.dp)
|
||||
height: parent.height / 2
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
style: ButtonStyle.numericPad
|
||||
shadowEnabled: true
|
||||
padding: Math.round(3 * DefaultStyle.dp)
|
||||
onClicked: contents.removeContent(modelData)
|
||||
}
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 2 * parent.height / 3
|
||||
}
|
||||
}
|
||||
Control.ScrollBar.horizontal: selectedFilesScrollbar
|
||||
}
|
||||
ScrollBar {
|
||||
id: selectedFilesScrollbar
|
||||
active: true
|
||||
anchors.bottom: selectedFilesArea.bottom
|
||||
anchors.left: selectedFilesArea.left
|
||||
anchors.right: selectedFilesArea.right
|
||||
contentItem: ListView {
|
||||
id: selectedFiles
|
||||
orientation: ListView.Horizontal
|
||||
spacing: Math.round(16 * DefaultStyle.dp)
|
||||
model: ChatMessageContentProxy {
|
||||
id: contents
|
||||
filterType: ChatMessageContentProxy.FilterContentType.File
|
||||
}
|
||||
delegate: Item {
|
||||
width: Math.round(80 * DefaultStyle.dp)
|
||||
height: Math.round(80 * DefaultStyle.dp)
|
||||
FileView {
|
||||
contentGui: modelData
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
width: Math.round(69 * DefaultStyle.dp)
|
||||
height: Math.round(69 * DefaultStyle.dp)
|
||||
}
|
||||
RoundButton {
|
||||
icon.source: AppIcons.closeX
|
||||
icon.width: Math.round(12 * DefaultStyle.dp)
|
||||
icon.height: Math.round(12 * DefaultStyle.dp)
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
style: ButtonStyle.numericPad
|
||||
shadowEnabled: true
|
||||
padding: Math.round(3 * DefaultStyle.dp)
|
||||
onClicked: contents.removeContent(modelData)
|
||||
}
|
||||
}
|
||||
Control.ScrollBar.horizontal: selectedFilesScrollbar
|
||||
}
|
||||
ScrollBar {
|
||||
id: selectedFilesScrollbar
|
||||
active: true
|
||||
anchors.bottom: selectedFilesArea.bottom
|
||||
anchors.left: selectedFilesArea.left
|
||||
anchors.right: selectedFilesArea.right
|
||||
}
|
||||
}
|
||||
}
|
||||
ChatDroppableTextArea {
|
||||
id: messageSender
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: mainItem.chat.core.isReadOnly ? 0 : height
|
||||
Control.SplitView.preferredHeight: mainItem.chat.core.isReadOnly ? 0 : Math.round(79 * DefaultStyle.dp)
|
||||
Control.SplitView.minimumHeight: mainItem.chat.core.isReadOnly ? 0 : Math.round(79 * DefaultStyle.dp)
|
||||
chat: mainItem.chat
|
||||
Component.onCompleted: {
|
||||
|
||||
if (mainItem.chat) text = mainItem.chat.core.sendingText
|
||||
}
|
||||
onTextChanged: {
|
||||
|
|
@ -270,12 +280,11 @@ RowLayout {
|
|||
}
|
||||
mainItem.chat.core.sendingText = text
|
||||
}
|
||||
onSendText: {
|
||||
onSendMessage: {
|
||||
var filesContents = contents.getAll()
|
||||
if (filesContents.length === 0)
|
||||
mainItem.chat.core.lSendTextMessage(text)
|
||||
else mainItem.chat.core.lSendMessage(text, filesContents)
|
||||
messageSender.textArea.clear()
|
||||
contents.clear()
|
||||
}
|
||||
onDropped: (files) => {
|
||||
|
|
|
|||
39
Linphone/view/Page/Layout/Settings/ChatSettingsLayout.qml
Normal file
39
Linphone/view/Page/Layout/Settings/ChatSettingsLayout.qml
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls.Basic as Control
|
||||
import SettingsCpp
|
||||
import Linphone
|
||||
|
||||
AbstractSettingsLayout {
|
||||
id: mainItem
|
||||
width: parent?.width
|
||||
contentModel: [
|
||||
{
|
||||
//: Attached files
|
||||
title: qsTr("settings_chat_attached_files_title"),
|
||||
subTitle: "",
|
||||
contentComponent: attachedFilesParamComp,
|
||||
// hideTopMargin: true
|
||||
}
|
||||
]
|
||||
|
||||
Component {
|
||||
id: attachedFilesParamComp
|
||||
SwitchSetting {
|
||||
//: "Automatic download"
|
||||
titleText: qsTr("settings_chat_attached_files_auto_download_title")
|
||||
//: "Automatically download transferred or received files in conversations"
|
||||
subTitleText: qsTr("settings_chat_attached_files_auto_download_subtitle")
|
||||
propertyName: "autoDownloadReceivedFiles"
|
||||
propertyOwner: SettingsCpp
|
||||
|
||||
Connections {
|
||||
target: mainItem
|
||||
function onSave() {
|
||||
SettingsCpp.save()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls.Basic as Control
|
||||
import SettingsCpp 1.0
|
||||
import SettingsCpp
|
||||
import Linphone
|
||||
|
||||
AbstractSettingsLayout {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
pragma Singleton
|
||||
import QtQuick
|
||||
import Linphone
|
||||
|
||||
QtObject {
|
||||
property color main1_100: "#FFEACB"
|
||||
|
|
|
|||
|
|
@ -38,6 +38,23 @@
|
|||
}
|
||||
}
|
||||
|
||||
// White with orange icon
|
||||
var player = {
|
||||
color: {
|
||||
normal: Linphone.DefaultStyle.grey_0,
|
||||
hovered: Linphone.DefaultStyle.main1_100,
|
||||
pressed: Linphone.DefaultStyle.main1_500_main
|
||||
},
|
||||
text: {
|
||||
normal: Linphone.DefaultStyle.main1_500_main,
|
||||
pressed: Linphone.DefaultStyle.main1_500_main
|
||||
},
|
||||
image: {
|
||||
normal: Linphone.DefaultStyle.main1_500_main,
|
||||
pressed: Linphone.DefaultStyle.main1_500_main
|
||||
}
|
||||
}
|
||||
|
||||
// Light orange
|
||||
var tertiary = {
|
||||
color: {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue