Vocal message on send :

- Record
- Play
- Seek by clicking on the sound wave
- Send with a text/file/reply
- Improve ActionButton for horizontal progression and background color managment
- Improve Icon with different height and width
- Creation of MediaProgressBar
This commit is contained in:
Julien Wadel 2021-11-11 00:36:50 +01:00
parent 3ea4dbd4ec
commit 9f781aa434
36 changed files with 1081 additions and 60 deletions

View file

@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Added
- Features:
* messages features : Reply, forward (to contact, to a SIP address or to a timeline)
* messages features : Reply, forward (to contact, to a SIP address or to a timeline), Vocal record (on send)
## 4.3.2

View file

@ -181,6 +181,8 @@ set(SOURCES
src/components/participant-imdn/ParticipantImdnStateProxyModel.cpp
src/components/presence/OwnPresenceModel.cpp
src/components/presence/Presence.cpp
src/components/recorder/RecorderManager.cpp
src/components/recorder/RecorderModel.cpp
src/components/search/SearchHandler.cpp
src/components/search/SearchResultModel.cpp
src/components/search/SearchSipAddressesModel.cpp
@ -288,6 +290,8 @@ set(HEADERS
src/components/participant-imdn/ParticipantImdnStateProxyModel.cpp
src/components/presence/OwnPresenceModel.hpp
src/components/presence/Presence.hpp
src/components/recorder/RecorderManager.hpp
src/components/recorder/RecorderModel.hpp
src/components/search/SearchHandler.hpp
src/components/search/SearchResultModel.hpp
src/components/search/SearchSipAddressesModel.hpp

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="80"
height="80"
viewBox="0 0 80 80"
version="1.1"
id="svg8"
sodipodi:docname="chat_audio_pause.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs12" />
<sodipodi:namedview
id="namedview10"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="5.3231707"
inkscape:cx="28.460481"
inkscape:cy="64.623139"
inkscape:window-width="1920"
inkscape:window-height="1043"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg8" />
<g
fill="none"
fill-rule="evenodd"
id="g6"
transform="scale(0.96277665,0.97560976)">
<g
fill="#444444"
id="g4">
<path
d="m 41.547,0 c 22.945,0 41.546,18.356 41.546,41 0,22.644 -18.6,41 -41.546,41 C 18.6,82 0,63.644 0,41 0,18.356 18.601,0 41.547,0 Z M 30.04,21.5 c -1.933,0 -3.5,1.567 -3.5,3.5 v 32 l 0.005,0.192 c 0.1,1.844 1.626,3.308 3.495,3.308 1.933,0 3.5,-1.567 3.5,-3.5 V 25 L 33.535,24.808 C 33.435,22.964 31.909,21.5 30.04,21.5 Z m 21,0 c -1.933,0 -3.5,1.567 -3.5,3.5 v 32 l 0.005,0.192 c 0.1,1.844 1.626,3.308 3.495,3.308 1.933,0 3.5,-1.567 3.5,-3.5 V 25 L 54.535,24.808 C 54.435,22.964 52.909,21.5 51.04,21.5 Z"
id="path2" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="80"
height="80"
viewBox="0 0 80 80"
version="1.1"
id="svg8"
sodipodi:docname="chat_audio_play.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs12" />
<sodipodi:namedview
id="namedview10"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="5.3231707"
inkscape:cx="29.024055"
inkscape:cy="63.683849"
inkscape:window-width="1920"
inkscape:window-height="1043"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg8" />
<g
fill="none"
fill-rule="evenodd"
id="g6"
transform="scale(0.96277665,0.97560976)">
<g
fill="#444444"
id="g4">
<path
d="m 41.547,0 c 22.945,0 41.546,18.356 41.546,41 0,22.644 -18.6,41 -41.546,41 C 18.6,82 0,63.644 0,41 0,18.356 18.601,0 41.547,0 Z M 29.975,20.97 V 59.812 L 61.75,41.12 Z"
id="path2" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="500"
height="60"
viewBox="0 0 500 60"
version="1.1"
id="svg8"
sodipodi:docname="chat_audio_soundwave_custom.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs12" />
<sodipodi:namedview
id="namedview10"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="1.2704082"
inkscape:cx="250.70682"
inkscape:cy="-59.036143"
inkscape:window-width="1920"
inkscape:window-height="1043"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg8" />
<g
fill="none"
fill-rule="evenodd"
id="g6"
transform="matrix(1.0204082,0,0,0.66666667,0,10)">
<g
fill="#444444"
fill-rule="nonzero"
id="g4">
<path
d="m 65,0 c 2.761,0 5,2.239 5,5 v 50 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 5 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 50 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 5 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 50 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 5 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 50 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 5 c 0,-2.761 2.239,-5 5,-5 z M 85,5 c 2.761,0 5,2.239 5,5 v 40 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 10 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 40 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 10 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 40 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 10 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 40 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 10 c 0,-2.761 2.239,-5 5,-5 z M 45,5 c 2.761,0 5,2.239 5,5 v 40 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 10 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 40 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 10 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 40 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 10 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 40 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 10 c 0,-2.761 2.239,-5 5,-5 z M 25,15 c 2.761,0 5,2.239 5,5 v 20 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 20 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 20 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 20 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 20 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 20 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 20 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 20 c 0,-2.761 2.239,-5 5,-5 z m -280,0 c 2.761,0 5,2.239 5,5 v 20 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 20 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 20 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 20 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 20 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 20 c 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 v 20 c 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 V 20 c 0,-2.761 2.239,-5 5,-5 z M 5,25 c 2.761,0 5,2.239 5,5 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 0,-2.761 2.239,-5 5,-5 z m 120,0 c 2.761,0 5,2.239 5,5 0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 0,-2.761 2.239,-5 5,-5 z"
id="path2" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4 KiB

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="80"
height="80"
viewBox="0 0 80 80"
version="1.1"
id="svg10"
sodipodi:docname="chat_audio_stop.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs14" />
<sodipodi:namedview
id="namedview12"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="5.3231707"
inkscape:cx="6.2932417"
inkscape:cy="62.368843"
inkscape:window-width="1920"
inkscape:window-height="1043"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg10" />
<g
fill="none"
fill-rule="evenodd"
id="g8"
transform="scale(0.96167716,0.97560976)">
<g
fill="#444444"
fill-rule="nonzero"
id="g6">
<g
id="g4">
<path
d="m 41.594,0 c 22.972,0 41.594,18.356 41.594,41 0,22.644 -18.622,41 -41.594,41 C 18.622,82 0,63.644 0,41 0,18.356 18.622,0 41.594,0 Z m 16.5,23 h -32 c -1.105,0 -2,0.895 -2,2 v 32 c 0,1.105 0.895,2 2,2 h 32 c 1.104,0 2,-0.895 2,-2 V 25 c 0,-1.105 -0.896,-2 -2,-2 z"
id="path2" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -30,6 +30,10 @@
<file>assets/images/cancel_custom.svg</file>
<file>assets/images/chat_custom.svg</file>
<file>assets/images/chat_amount.svg</file>
<file>assets/images/chat_audio_pause_custom.svg</file>
<file>assets/images/chat_audio_play_custom.svg</file>
<file>assets/images/chat_audio_soundwave_custom.svg</file>
<file>assets/images/chat_audio_stop_custom.svg</file>
<file>assets/images/chat_count.svg</file>
<file>assets/images/chat_delivered.svg</file>
<file>assets/images/chat_error.svg</file>
@ -193,6 +197,7 @@
<file>ui/modules/Common/Helpers/InvertedMouseArea.qml</file>
<file>ui/modules/Common/Image/Icon.qml</file>
<file>ui/modules/Common/Image/RoundedImage.qml</file>
<file>ui/modules/Common/Indicators/MediaProgressBar.qml</file>
<file>ui/modules/Common/Indicators/VuMeter.qml</file>
<file>ui/modules/Common/Menus/ApplicationMenuEntry.qml</file>
<file>ui/modules/Common/Menus/ApplicationMenu.qml</file>
@ -238,6 +243,7 @@
<file>ui/modules/Common/Styles/Form/Tab/TabButtonStyle.qml</file>
<file>ui/modules/Common/Styles/Form/Tab/TabContainerStyle.qml</file>
<file>ui/modules/Common/Styles/Form/TransparentTextInputStyle.qml</file>
<file>ui/modules/Common/Styles/Indicators/MediaProgressBarStyle.qml</file>
<file>ui/modules/Common/Styles/Indicators/VuMeterStyle.qml</file>
<file>ui/modules/Common/Styles/Menus/ApplicationMenuStyle.qml</file>
<file>ui/modules/Common/Styles/Menus/DropDownStaticMenuStyle.qml</file>
@ -270,6 +276,7 @@
<file>ui/modules/Linphone/Chat/Chat.qml</file>
<file>ui/modules/Linphone/Chat/ChatDeliveries.qml</file>
<file>ui/modules/Linphone/Chat/ChatMenu.qml</file>
<file>ui/modules/Linphone/Chat/ChatAudioPreview.qml</file>
<file>ui/modules/Linphone/Chat/ChatMessagePreview.qml</file>
<file>ui/modules/Linphone/Chat/ChatForwardMessage.qml</file>
<file>ui/modules/Linphone/Chat/ChatReplyMessage.qml</file>
@ -314,6 +321,7 @@
<file>ui/modules/Linphone/Styles/Calls/CallStatisticsStyle.qml</file>
<file>ui/modules/Linphone/Styles/Calls/ConferenceControlsStyle.qml</file>
<file>ui/modules/Linphone/Styles/Chat/ChatStyle.qml</file>
<file>ui/modules/Linphone/Styles/Chat/ChatAudioPreviewStyle.qml</file>
<file>ui/modules/Linphone/Styles/Chat/ChatForwardMessageStyle.qml</file>
<file>ui/modules/Linphone/Styles/Chat/ChatReplyMessageStyle.qml</file>
<file>ui/modules/Linphone/Styles/Codecs/CodecsViewerStyle.qml</file>

View file

@ -533,6 +533,14 @@ static inline void registerSharedSingletonType (const char *name) {
qmlRegisterSingletonType<T>(Constants::MainQmlUri, 1, 0, name, makeSharedSingleton<T, function>);
}
template<typename T, T *(CoreManager::*function)()>
static QObject *makeSharedSingleton (QQmlEngine *, QJSEngine *) {
QObject *object = (CoreManager::getInstance()->*function)();
QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership);
return object;
}
template<typename T, T *(CoreManager::*function)() const>
static QObject *makeSharedSingleton (QQmlEngine *, QJSEngine *) {
QObject *object = (CoreManager::getInstance()->*function)();
@ -545,6 +553,11 @@ static inline void registerSharedSingletonType (const char *name) {
qmlRegisterSingletonType<T>(Constants::MainQmlUri, 1, 0, name, makeSharedSingleton<T, function>);
}
template<typename T, T *(CoreManager::*function)()>
static inline void registerSharedSingletonType (const char *name) {
qmlRegisterSingletonType<T>(Constants::MainQmlUri, 1, 0, name, makeSharedSingleton<T, function>);
}
template<typename T>
static inline void registerUncreatableType (const char *name) {
qmlRegisterUncreatableType<T>(Constants::MainQmlUri, 1, 0, name, QLatin1String("Uncreatable"));
@ -647,6 +660,7 @@ void App::registerTypes () {
registerUncreatableType<ContentModel>("ContentModel");
registerUncreatableType<HistoryModel>("HistoryModel");
registerUncreatableType<LdapModel>("LdapModel");
registerUncreatableType<RecorderModel>("RecorderModel");
registerUncreatableType<SearchResultModel>("SearchResultModel");
registerUncreatableType<SipAddressObserver>("SipAddressObserver");
registerUncreatableType<VcardModel>("VcardModel");
@ -680,6 +694,7 @@ void App::registerSharedTypes () {
registerSharedSingletonType<ContactsImporterListModel, &CoreManager::getContactsImporterListModel>("ContactsImporterListModel");
registerSharedSingletonType<LdapListModel, &CoreManager::getLdapListModel>("LdapListModel");
registerSharedSingletonType<TimelineListModel, &CoreManager::getTimelineListModel>("TimelineListModel");
registerSharedSingletonType<RecorderManager, &CoreManager::getRecorderManager>("RecorderManager");
//qmlRegisterSingletonType<ColorListModel>(Constants::MainQmlUri, 1, 0, "ColorList", mColorListModel);

View file

@ -63,6 +63,8 @@
#include "participant-imdn/ParticipantImdnStateListModel.hpp"
#include "participant-imdn/ParticipantImdnStateProxyModel.hpp"
#include "presence/OwnPresenceModel.hpp"
#include "recorder/RecorderModel.hpp"
#include "recorder/RecorderManager.hpp"
#include "settings/AccountSettingsModel.hpp"
#include "settings/SettingsModel.hpp"
#include "search/SearchResultModel.hpp"

View file

@ -52,6 +52,8 @@
#include "components/participant/ParticipantModel.hpp"
#include "components/participant/ParticipantListModel.hpp"
#include "components/presence/Presence.hpp"
#include "components/recorder/RecorderManager.hpp"
#include "components/recorder/RecorderModel.hpp"
#include "components/timeline/TimelineModel.hpp"
#include "components/timeline/TimelineListModel.hpp"
#include "components/core/event-count-notifier/AbstractEventCountNotifier.hpp"
@ -600,6 +602,7 @@ void ChatRoomModel::setReply(ChatMessageModel * model){
void ChatRoomModel::clearReply(){
mReply = nullptr;
}
//------------------------------------------------------------------------------------------------
void ChatRoomModel::deleteChatRoom(){
@ -639,13 +642,18 @@ void ChatRoomModel::updateParticipants(const QVariantList& participants){
void ChatRoomModel::sendMessage (const QString &message) {
shared_ptr<linphone::ChatMessage> _message;
if(mReply){
if(mReply)
_message = mChatRoom->createReplyMessage(mReply);
_message->addUtf8TextContent(message.toUtf8().toStdString());
}else{
_message= mChatRoom->createMessageFromUtf8(message.toUtf8().toStdString());
else
_message= mChatRoom->createEmptyMessage();
auto recorder = CoreManager::getInstance()->getRecorderManager();
if(recorder->haveVocalRecorder()) {
auto content = recorder->getVocalRecorder()->getRecorder()->createContent();
if(content)
_message->addContent(content);
}
if(!message.isEmpty())
_message->addUtf8TextContent(message.toUtf8().toStdString());
_message->send();
emit messageSent(_message);
}
@ -677,6 +685,13 @@ void ChatRoomModel::sendFileMessage (const QString &path) {
shared_ptr<linphone::ChatMessage> message = mChatRoom->createFileTransferMessage(content);
message->getContents().front()->setFilePath(Utils::appStringToCoreString(path));
auto recorder = CoreManager::getInstance()->getRecorderManager();
if(recorder->haveVocalRecorder()) {
auto content = recorder->getVocalRecorder()->getRecorder()->createContent();
if(content)
message->addContent(content);
}
message->send();
emit messageSent(message);
@ -686,6 +701,12 @@ void ChatRoomModel::forwardMessage(ChatMessageModel * model){
if(model){
shared_ptr<linphone::ChatMessage> _message;
_message = mChatRoom->createForwardMessage(model->getChatMessage());
auto recorder = CoreManager::getInstance()->getRecorderManager();
if(recorder->haveVocalRecorder()) {
auto content = recorder->getVocalRecorder()->getRecorder()->createContent();
if(content)
_message->addContent(content);
}
_message->send();
emit messageSent(_message);
}

View file

@ -36,6 +36,7 @@
#include "components/contacts/ContactsImporterListModel.hpp"
#include "components/history/HistoryModel.hpp"
#include "components/ldap/LdapListModel.hpp"
#include "components/recorder/RecorderManager.hpp"
#include "components/settings/AccountSettingsModel.hpp"
#include "components/settings/SettingsModel.hpp"
#include "components/sip-addresses/SipAddressesModel.hpp"
@ -123,6 +124,14 @@ HistoryModel* CoreManager::getHistoryModel(){
}
return mHistoryModel;
}
RecorderManager* CoreManager::getRecorderManager(){
if(!mRecorderManager){
mRecorderManager = new RecorderManager(this);
emit recorderManagerCreated(mRecorderManager);
}
return mRecorderManager;
}
// -----------------------------------------------------------------------------
void CoreManager::init (QObject *parent, const QString &configPath) {
@ -183,6 +192,7 @@ void CoreManager::cleanLogs () const {
mCore->resetLogCollection();
}
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------

View file

@ -42,6 +42,7 @@ class CoreHandlers;
class EventCountNotifier;
class HistoryModel;
class LdapListModel;
class RecorderManager;
class SettingsModel;
class SipAddressesModel;
class VcardModel;
@ -76,6 +77,7 @@ public:
//bool chatRoomModelExists (std::shared_ptr<linphone::ChatRoom> chatRoom);
HistoryModel* getHistoryModel();
RecorderManager* getRecorderManager();
// ---------------------------------------------------------------------------
// Video render lock.
@ -179,6 +181,7 @@ signals:
void chatRoomModelCreated (const std::shared_ptr<ChatRoomModel> &chatRoomModel);
void historyModelCreated (HistoryModel *historyModel);
void recorderManagerCreated(RecorderManager *recorderModel);
void logsUploaded (const QString &url);
@ -227,6 +230,7 @@ private:
//QList<QPair<std::shared_ptr<linphone::ChatRoom>, std::weak_ptr<ChatRoomModel>>> mChatRoomModels;
HistoryModel * mHistoryModel = nullptr;
LdapListModel *mLdapListModel = nullptr;
RecorderManager* mRecorderManager = nullptr;
QTimer *mCbsTimer = nullptr;

View file

@ -0,0 +1,71 @@
/*
* Copyright (c) 2021 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QQmlApplicationEngine>
#include <QTimer>
#include "app/App.hpp"
#include "components/core/CoreManager.hpp"
#include "RecorderManager.hpp"
#include "RecorderModel.hpp"
// =============================================================================
RecorderManager::RecorderManager (QObject * parent) : QObject(parent) {
App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it when passing by Q_INVOKABLE
}
RecorderManager::~RecorderManager(){
}
bool RecorderManager::haveVocalRecorder() const{
return mVocalRecorder != nullptr;
}
RecorderModel* RecorderManager::getVocalRecorder(){
if( !mVocalRecorder) {
auto core = CoreManager::getInstance()->getCore();
std::shared_ptr<linphone::RecorderParams> params = core->createRecorderParams();
params->setFileFormat(linphone::RecorderFileFormat::Mkv);
params->setVideoCodec("");
auto recorder = core->createRecorder(params);
if(recorder)
mVocalRecorder = RecorderModel::create(recorder, this);
emit haveVocalRecorderChanged();
}
return mVocalRecorder.get();
}
RecorderModel* RecorderManager::resetVocalRecorder(){
if(mVocalRecorder)
clearVocalRecorder();
return getVocalRecorder();
}
void RecorderManager::clearVocalRecorder(){
if( mVocalRecorder){
mVocalRecorder = nullptr;
emit haveVocalRecorderChanged();
}
}
//--------------------------------------------------------------------------------------------------------------------------

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2021 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RECORDER_MANAGER_MODEL_H
#define RECORDER_MANAGER_MODEL_H
#include <linphone++/linphone.hh>
#include <QObject>
// =============================================================================
class RecorderModel;
class RecorderManager : public QObject {
Q_OBJECT
public:
RecorderManager (QObject * parent = nullptr);
virtual ~RecorderManager();
Q_PROPERTY(bool haveVocalRecorder READ haveVocalRecorder NOTIFY haveVocalRecorderChanged)
bool haveVocalRecorder() const;
Q_INVOKABLE RecorderModel* getVocalRecorder();
Q_INVOKABLE RecorderModel* resetVocalRecorder();
Q_INVOKABLE void clearVocalRecorder();
signals:
void haveVocalRecorderChanged();
private:
std::shared_ptr<RecorderModel> mVocalRecorder;
};
#endif

View file

@ -0,0 +1,95 @@
/*
* Copyright (c) 2021 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QQmlApplicationEngine>
#include <QFile>
#include "app/App.hpp"
#include "app/paths/Paths.hpp"
#include "components/core/CoreManager.hpp"
#include "components/settings/SettingsModel.hpp"
#include "utils/Utils.hpp"
#include "RecorderModel.hpp"
// =============================================================================
RecorderModel::RecorderModel ( std::shared_ptr<linphone::Recorder> recorder, QObject * parent) : QObject(parent) {
App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it when passing by Q_INVOKABLE
mRecorder= recorder;
}
RecorderModel::~RecorderModel(){
}
std::shared_ptr<RecorderModel> RecorderModel::create(std::shared_ptr<linphone::Recorder> recorder, QObject * parent){
return std::make_shared<RecorderModel>(recorder, parent);
}
std::shared_ptr<linphone::Recorder> RecorderModel::getRecorder(){
return mRecorder;
}
int RecorderModel::getDuration()const{
return mRecorder->getDuration();
}
LinphoneEnums::RecorderState RecorderModel::getState() const{
return LinphoneEnums::fromLinphone(mRecorder->getState());
}
QString RecorderModel::getFile()const{
return Utils::coreStringToAppString(mRecorder->getFile());
}
void RecorderModel::start(){
bool soFarSoGood;
QString filename = QStringLiteral("vocal_%1.mkv")
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd_hh-mm-ss-zzz"));
const QString safeFilePath = Utils::getSafeFilePath(
QStringLiteral("%1%2")
.arg(Utils::coreStringToAppString(Paths::getCapturesDirPath()))
.arg(filename),
&soFarSoGood
);
if (!soFarSoGood) {
qWarning() << QStringLiteral("Unable to create safe file path for: %1.").arg(filename);
}else if(mRecorder->open(Utils::appStringToCoreString(safeFilePath)) < 0)
qWarning() << QStringLiteral("Unable to open safe file path for: %1.").arg(filename);
else if( mRecorder->start() < 0)
qWarning() << QStringLiteral("Unable to start recording to : %1.").arg(filename);
emit stateChanged();
emit fileChanged();
}
void RecorderModel::pause(){
qWarning() << mRecorder->pause();
emit stateChanged();
}
void RecorderModel::stop(){
if(mRecorder->pause() == 0)
mRecorder->close();
emit stateChanged();
}
//--------------------------------------------------------------------------------------------------------------------------

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2021 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RECORDER_MODEL_H
#define RECORDER_MODEL_H
#include "utils/LinphoneEnums.hpp"
// =============================================================================
class RecorderModel : public QObject {
Q_OBJECT
public:
static std::shared_ptr<RecorderModel> create(std::shared_ptr<linphone::Recorder> recorder,QObject * parent = nullptr);// Call it instead constructor
RecorderModel (std::shared_ptr<linphone::Recorder> recorder,QObject * parent = nullptr);
virtual ~RecorderModel();
Q_PROPERTY(LinphoneEnums::RecorderState state READ getState NOTIFY stateChanged)
Q_PROPERTY(QString file READ getFile NOTIFY fileChanged)
std::shared_ptr<linphone::Recorder> getRecorder();
Q_INVOKABLE int getDuration()const;
LinphoneEnums::RecorderState getState() const;
Q_INVOKABLE QString getFile()const;
Q_INVOKABLE void start();
Q_INVOKABLE void pause();
Q_INVOKABLE void stop();
signals:
void stateChanged();
void fileChanged();
private:
std::shared_ptr<linphone::Recorder> mRecorder;
};
Q_DECLARE_METATYPE(std::shared_ptr<RecorderModel>)
Q_DECLARE_METATYPE(RecorderModel*)
#endif

View file

@ -37,11 +37,11 @@ namespace linphone {
class SoundPlayer : public QObject {
class Handlers;
Q_OBJECT;
Q_OBJECT
Q_PROPERTY(QString source READ getSource WRITE setSource NOTIFY sourceChanged);
Q_PROPERTY(PlaybackState playbackState READ getPlaybackState WRITE setPlaybackState NOTIFY playbackStateChanged);
Q_PROPERTY(int duration READ getDuration NOTIFY sourceChanged);
Q_PROPERTY(QString source READ getSource WRITE setSource NOTIFY sourceChanged)
Q_PROPERTY(PlaybackState playbackState READ getPlaybackState WRITE setPlaybackState NOTIFY playbackStateChanged)
Q_PROPERTY(int duration READ getDuration NOTIFY sourceChanged)
public:
enum PlaybackState {

View file

@ -29,9 +29,11 @@ void LinphoneEnums::registerMetaTypes(){
qRegisterMetaType<LinphoneEnums::FriendCapability>();
qRegisterMetaType<LinphoneEnums::EventLogType>();
qRegisterMetaType<LinphoneEnums::ChatMessageState>();
qRegisterMetaType<LinphoneEnums::CallStatus>();
qRegisterMetaType<LinphoneEnums::TunnelMode>();
qRegisterMetaType<LinphoneEnums::RecorderState>();
}
linphone::MediaEncryption LinphoneEnums::toLinphone(const LinphoneEnums::MediaEncryption& data){
return static_cast<linphone::MediaEncryption>(data);
}
@ -73,3 +75,10 @@ linphone::Tunnel::Mode LinphoneEnums::toLinphone(const LinphoneEnums::TunnelMode
LinphoneEnums::TunnelMode LinphoneEnums::fromLinphone(const linphone::Tunnel::Mode& data){
return static_cast<LinphoneEnums::TunnelMode>(data);
}
linphone::RecorderState LinphoneEnums::toLinphone(const LinphoneEnums::RecorderState& data){
return static_cast<linphone::RecorderState>(data);
}
LinphoneEnums::RecorderState LinphoneEnums::fromLinphone(const linphone::RecorderState& data){
return static_cast<LinphoneEnums::RecorderState>(data);
}

View file

@ -121,6 +121,16 @@ Q_ENUM_NS(TunnelMode)
linphone::Tunnel::Mode toLinphone(const LinphoneEnums::TunnelMode& mode);
LinphoneEnums::TunnelMode fromLinphone(const linphone::Tunnel::Mode& mode);
enum RecorderState{
RecorderStateClosed = int(linphone::RecorderState::Closed),
RecorderStatePaused = int(linphone::RecorderState::Paused),
RecorderStateRunning = int(linphone::RecorderState::Running)
};
Q_ENUM_NS(RecorderState)
linphone::RecorderState toLinphone(const LinphoneEnums::RecorderState& state);
LinphoneEnums::RecorderState fromLinphone(const linphone::RecorderState& state);
}
Q_DECLARE_METATYPE(LinphoneEnums::MediaEncryption)
@ -129,5 +139,6 @@ Q_DECLARE_METATYPE(LinphoneEnums::EventLogType)
Q_DECLARE_METATYPE(LinphoneEnums::ChatMessageState)
Q_DECLARE_METATYPE(LinphoneEnums::CallStatus)
Q_DECLARE_METATYPE(LinphoneEnums::TunnelMode)
Q_DECLARE_METATYPE(LinphoneEnums::RecorderState)
#endif

View file

@ -1,5 +1,6 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.12
import Common 1.0
@ -33,6 +34,8 @@ Item {
property bool useStates: true
//property bool autoIcon : false // hovered/pressed : use an automatic layer instead of specific icon image
property int iconSize : colorSet.iconSize
property int iconHeight: colorSet.iconHeight ? colorSet.iconHeight : 0
property int iconWidth: colorSet.iconWidth ? colorSet.iconWidth : 0
readonly property alias hovered: button.hovered
property alias text: button.text
// Tooltip aliases
@ -44,24 +47,34 @@ Item {
property alias backgroundRadius : backgroundColor.radius
property alias horizontalAlignment: icon.horizontalAlignment
property alias verticalAlignment: icon.verticalAlignment
property alias fillMode: icon.fillMode
// AutoColor for hide part alpha /4
property color foregroundHiddenPartNormalColor : colorSet.foregroundNormalColor ? Qt.rgba(colorSet.foregroundNormalColor.r, colorSet.foregroundNormalColor.g, colorSet.foregroundNormalColor.b, colorSet.foregroundNormalColor.a/4) : 'transparent'
property color foregroundHiddenPartDisabledColor : colorSet.foregroundDisabledColor ? Qt.rgba(colorSet.foregroundDisabledColor.r, colorSet.foregroundDisabledColor.g, colorSet.foregroundDisabledColor.b, colorSet.foregroundDisabledColor.a/4): 'transparent'
property color foregroundHiddenPartHoveredColor : colorSet.foregroundHoveredColor ? Qt.rgba(colorSet.foregroundHoveredColor.r, colorSet.foregroundHoveredColor.g, colorSet.foregroundHoveredColor.b, colorSet.foregroundHoveredColor.a/4): 'transparent'
property color foregroundHiddenPartUpdatingColor : colorSet.foregroundUpdatingColor ? Qt.rgba(colorSet.foregroundUpdatingColor.r, colorSet.foregroundUpdatingColor.g, colorSet.foregroundUpdatingColor.b, colorSet.foregroundUpdatingColor.a/4): 'transparent'
property color foregroundHiddenPartPressedColor : colorSet.foregroundPressedColor ? Qt.rgba(colorSet.foregroundPressedColor.r, colorSet.foregroundPressedColor.g, colorSet.foregroundPressedColor.b, colorSet.foregroundPressedColor.a/4): 'transparent'
// Hidden part : transparent if not specified
property color backgroundHiddenPartNormalColor : colorSet.backgroundHiddenPartNormalColor ? colorSet.backgroundHiddenPartNormalColor : (colorSet.backgroundNormalColor ? colorSet.backgroundNormalColor : 'transparent')
property color backgroundHiddenPartDisabledColor : colorSet.backgroundHiddenPartDisabledColor ? colorSet.backgroundHiddenPartDisabledColor : (colorSet.backgroundDisabledColor ? colorSet.backgroundDisabledColor : 'transparent')
property color backgroundHiddenPartHoveredColor : colorSet.backgroundHiddenPartHoveredColor ? colorSet.backgroundHiddenPartHoveredColor : (colorSet.backgroundHoveredColor ? colorSet.backgroundHoveredColor : 'transparent')
property color backgroundHiddenPartUpdatingColor : colorSet.backgroundHiddenPartUpdatingColor ? colorSet.backgroundHiddenPartUpdatingColor : (colorSet.backgroundUpdatingColor ? colorSet.backgroundUpdatingColor : 'transparent')
property color backgroundHiddenPartPressedColor : colorSet.backgroundHiddenPartPressedColor ? colorSet.backgroundHiddenPartPressedColor : (colorSet.backgroundPressedColor ? colorSet.backgroundPressedColor : 'transparent')
// AutoColor : alpha /4 for foreground
property color foregroundHiddenPartNormalColor : colorSet.foregroundHiddenPartNormalColor ? colorSet.foregroundHiddenPartNormalColor : (colorSet.foregroundNormalColor ? Qt.rgba(colorSet.foregroundNormalColor.r, colorSet.foregroundNormalColor.g, colorSet.foregroundNormalColor.b, colorSet.foregroundNormalColor.a/4) : 'transparent')
property color foregroundHiddenPartDisabledColor : colorSet.foregroundHiddenPartDisabledColor ? colorSet.foregroundHiddenPartDisabledColor : (colorSet.foregroundDisabledColor ? Qt.rgba(colorSet.foregroundDisabledColor.r, colorSet.foregroundDisabledColor.g, colorSet.foregroundDisabledColor.b, colorSet.foregroundDisabledColor.a/4): 'transparent')
property color foregroundHiddenPartHoveredColor : colorSet.foregroundHiddenPartHoveredColor ? colorSet.foregroundHiddenPartHoveredColor : (colorSet.foregroundHoveredColor ? Qt.rgba(colorSet.foregroundHoveredColor.r, colorSet.foregroundHoveredColor.g, colorSet.foregroundHoveredColor.b, colorSet.foregroundHoveredColor.a/4): 'transparent')
property color foregroundHiddenPartUpdatingColor : colorSet.foregroundHiddenPartUpdatingColor ? colorSet.foregroundHiddenPartUpdatingColor : (colorSet.foregroundUpdatingColor ? Qt.rgba(colorSet.foregroundUpdatingColor.r, colorSet.foregroundUpdatingColor.g, colorSet.foregroundUpdatingColor.b, colorSet.foregroundUpdatingColor.a/4): 'transparent')
property color foregroundHiddenPartPressedColor : colorSet.foregroundHiddenPartPressedColor ? colorSet.foregroundHiddenPartPressedColor : (colorSet.foregroundPressedColor ? Qt.rgba(colorSet.foregroundPressedColor.r, colorSet.foregroundPressedColor.g, colorSet.foregroundPressedColor.b, colorSet.foregroundPressedColor.a/4): 'transparent')
//---------------------------------------------
property int percentageDisplayed : 100
// If `useStates` = true, the used icons are:
// `icon`_pressed, `icon`_hovered and `icon`_normal.
property string icon : colorSet.icon
// ---------------------------------------------------------------------------
signal clicked
signal clicked(real x, real y)
// ---------------------------------------------------------------------------
@ -120,6 +133,21 @@ Item {
}else
return "black"
}
function getBackgroundHiddenPartColor(){
if(isCustom){
if(wrappedButton.icon == '')
return wrappedButton.backgroundHiddenPartNormalColor
if (wrappedButton.updating)
return wrappedButton.backgroundHiddenPartUpdatingColor
if (!useStates)
return wrappedButton.backgroundHiddenPartNormalColor
if (!wrappedButton.enabled)
return wrappedButton.backgroundHiddenPartDisabledColor
return button.down ? wrappedButton.backgroundHiddenPartPressedColor
: (button.hovered ? wrappedButton.backgroundHiddenPartHoveredColor: wrappedButton.backgroundHiddenPartNormalColor)
}else
return 'transparent'
}
function getForegroundHiddenPartColor(){
if(isCustom){
if(wrappedButton.icon == '')
@ -137,20 +165,31 @@ Item {
}
// ---------------------------------------------------------------------------
height: iconSize || parent.iconSize || parent.height
width: iconSize || parent.iconSize || parent.width
height: iconHeight || iconSize || parent.iconSize || parent.height
width: iconWidth || iconSize || parent.iconSize || parent.width
Button {
id: button
anchors.fill: parent
background: Rectangle {
id: backgroundColor
color: getBackgroundColor()
}
background: Row{
anchors.fill: parent
Rectangle {
height: parent.height
width:parent.width * wrappedButton.percentageDisplayed / 100
id: backgroundColor
color: getBackgroundColor()
}
Rectangle {
height: parent.height
width: parent.width * ( 1 - wrappedButton.percentageDisplayed / 100 )
id: backgroundHiddenPartColor
color: getBackgroundHiddenPartColor()
}
}
hoverEnabled: !wrappedButton.updating//|| wrappedButton.autoIcon
onClicked: !wrappedButton.updating && wrappedButton.enabled && wrappedButton.clicked()
onClicked: !wrappedButton.updating && wrappedButton.enabled && wrappedButton.clicked(pressX, pressY)
Rectangle{
id: foregroundColor
anchors.fill:parent
@ -179,6 +218,7 @@ Item {
id: icon
anchors.centerIn: parent
anchors.fill: iconHeight>0 || iconWidth ? parent : undefined
icon: {
if(!Images[_getIcon()])
console.log("No images for: "+_getIcon())
@ -187,6 +227,8 @@ Item {
iconSize: wrappedButton.iconSize || (
parent.width > parent.height ? parent.height : parent.width
)
iconHeight: wrappedButton.iconHeight
iconWidth: wrappedButton.iconWidth
visible: !isCustom
}

View file

@ -4,6 +4,7 @@ import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.12
import Common 1.0
import Linphone 1.0
import Common.Styles 1.0
import Utils 1.0
@ -30,6 +31,7 @@ Item {
signal dropped (var files)
signal validText (string text)
signal audioRecordRequest()
// ---------------------------------------------------------------------------
@ -86,7 +88,7 @@ Item {
}
// Record audio
ActionButton {
visible:false && droppableTextArea.enabled// TODO
visible:droppableTextArea.enabled
id: recordAudioButton
//anchors.verticalCenter: parent.verticalCenter
@ -97,9 +99,7 @@ Item {
backgroundRadius: 8
colorSet: DroppableTextAreaStyle.chatMicro
useStates:false
onClicked: {console.log('Record audio request')}
onClicked: droppableTextArea.audioRecordRequest()
}
@ -137,7 +137,7 @@ Item {
}
}
function handleValidation () {
if (text.length !== 0) {
if (RecorderManager.haveVocalRecorder || text.length !== 0) {
validText(text)
}
}

View file

@ -12,40 +12,40 @@ import Utils 1.0
Item {
id: mainItem
property var iconSize // Required.
property int iconHeight: 0 // Or this
property int iconWidth: 0 // <-- too
property string icon
property color overwriteColor
property alias horizontalAlignment: image.horizontalAlignment
property alias verticalAlignment: image.verticalAlignment
property alias fillMode: image.fillMode
// Use this slot because of testing overwriteColor in layer doesn't seem to work
onOverwriteColorChanged: if(overwriteColor)
image.colorOverwriteEnabled = true
else
image.colorOverwriteEnabled = false
height: iconSize
width: iconSize
height: iconHeight > 0 ? iconHeight : iconSize
width: iconWidth > 0 ? iconWidth : iconSize
Image {
id:image
property bool colorOverwriteEnabled : false
mipmap: SettingsModel.mipmapEnabled
cache: Images.areReadOnlyImages
function getIconSize () {
Utils.assert(
(icon == null || icon == '' || iconSize != null && iconSize >= 0),
'`iconSize` must be defined and must be positive. (icon=`' +
icon + '`, iconSize=' + iconSize + ')'
)
return iconSize
}
cache: Images.areReadOnlyImages
anchors.centerIn: parent
//anchors.centerIn: parent
anchors.fill: parent
width: iconSize
height: iconSize
//width: iconWidth > 0 ? iconWidth : mainItem.width
//height: iconHeight > 0 ? iconHeight : mainItem.height
fillMode: Image.PreserveAspectFit
source: Utils.resolveImageUri(icon)
sourceSize.width: getIconSize()
sourceSize.height: getIconSize()
sourceSize.width: iconWidth > 0 ? iconWidth : iconSize
sourceSize.height: iconHeight > 0 ? iconHeight : iconSize
layer {
enabled: image.colorOverwriteEnabled
effect: ColorOverlay {

View file

@ -0,0 +1,107 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.12
import Common 1.0
import Linphone 1.0
import Utils 1.0
import Units 1.0
import Common.Styles 1.0
// =============================================================================
ProgressBar {
id: progressBar
property bool stopAtEnd: true
property bool resetAtEnd: false
property int progressDuration // Max duration
property int progressPosition // Position of pregress bar in [0 ; progressDuration]
property alias colorSet: progression.colorSet
function start(){
progressBar.value = 0
animationTest.start()
}
function resume(){
if(progressBar.value >= 100)
progressBar.value = 0
animationTest.start()
}
function stop(){
animationTest.stop()
}
signal endReached()
signal refreshPositionRequested()
signal seekRequested(int ms)
Timer{
id: animationTest
repeat: true
onTriggered: progressBar.refreshPositionRequested()
interval: 5
}
to: 101
value: 0
onValueChanged:{
if(value > 100){
if( progressBar.stopAtEnd)
stop()
if(progressBar.resetAtEnd) {
progressBar.value = 0
progressPosition = 0
}else{
progressBar.value = 100// Stay at 100
progressPosition = progressDuration
}
progressBar.endReached()
}else
progression.percentageDisplayed = value
}
anchors.topMargin: 5
anchors.bottomMargin: 5
background: Rectangle {
color: MediaProgressBarStyle.backgroundColor
radius: 5
}
contentItem:
Rectangle{
anchors.fill: parent
radius: 5
RowLayout{
anchors.fill: parent
ActionButton{
id: progression
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: 5
Layout.bottomMargin: 5
Layout.leftMargin: 10
backgroundRadius: 5
fillMode: Image.TileHorizontally
verticalAlignment: Image.AlignLeft
isCustom: true
colorSet: MediaProgressBarStyle.progressionWave
percentageDisplayed: 0
onClicked: progressBar.seekRequested(x * progressBar.progressDuration/width)
}
Text{
Layout.fillHeight: true
Layout.preferredWidth: 100
Layout.rightMargin: 10
horizontalAlignment: Qt.AlignRight
verticalAlignment: Qt.AlignVCenter
text: progressBar.progressPosition >= 0 ? Utils.formatElapsedTime( progressBar.progressPosition / 1000 ) : '-'
property font customFont : SettingsModel.textMessageFont
font.family: customFont.family
font.pointSize: Units.dp * (customFont.pointSize + 2)
}
}
}
}

View file

@ -0,0 +1,36 @@
pragma Singleton
import QtQml 2.2
import ColorsList 1.0
// =============================================================================
QtObject {
property string sectionName: 'MediaProgressBar'
property color backgroundColor: ColorsList.add(sectionName+'_bg', 'k').color
property string gaugeIcon: 'chat_audio_soundwave_custom'
property QtObject progressionWave: QtObject{
property int iconSize: 30
property int iconHeight: 40
property int iconWidth: 250
property string name : 'progression_soundwave'
property string icon : 'chat_audio_soundwave_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'a_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'a_h_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'a_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'a_n_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'a_h_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'a_p_b_fg').color
property color backgroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_n', icon, 'l_n_b_bg').color
property color backgroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_h', icon, 'l_h_b_bg').color
property color backgroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_p', icon, 'l_p_b_bg').color
property color foregroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_n', icon, 'l_n_b_fg').color
property color foregroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_h', icon, 'l_h_b_fg').color
property color foregroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_p', icon, 'l_p_b_fg').color
}
}

View file

@ -41,6 +41,7 @@ singleton FormVGroupStyle 1.0 Form/Placements/FormVGroupStyle.qml
singleton TabButtonStyle 1.0 Form/Tab/TabButtonStyle.qml
singleton TabContainerStyle 1.0 Form/Tab/TabContainerStyle.qml
singleton MediaProgressBarStyle 1.0 Indicators/MediaProgressBarStyle.qml
singleton VuMeterStyle 1.0 Indicators/VuMeterStyle.qml
singleton ApplicationMenuStyle 1.0 Menus/ApplicationMenuStyle.qml

View file

@ -65,6 +65,7 @@ InvertedMouseArea 1.0 Helpers/InvertedMouseArea.qml
Icon 1.0 Image/Icon.qml
RoundedImage 1.0 Image/RoundedImage.qml
MediaProgressBar 1.0 Indicators/MediaProgressBar.qml
VuMeter 1.0 Indicators/VuMeter.qml
ApplicationMenu 1.0 Menus/ApplicationMenu.qml

View file

@ -426,6 +426,7 @@ Rectangle {
chatMessagePreview.hide()
}
}
onAudioRecordRequest: RecorderManager.resetVocalRecorder()
Component.onCompleted: {text = proxyModel.cachedText; cursorPosition=text.length}
Rectangle{
anchors.fill:parent

View file

@ -0,0 +1,155 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Common 1.0
import Linphone 1.0
import Linphone.Styles 1.0
import Utils 1.0
import UtilsCpp 1.0
import LinphoneEnums 1.0
import Units 1.0
import 'Chat.js' as Logic
// =============================================================================
Rectangle{
id: audioPreviewBlock
property bool haveRecorder: RecorderManager.haveVocalRecorder
property RecorderModel vocalRecorder : (haveRecorder ? RecorderManager.getVocalRecorder() : null)
property bool isRecording : (vocalRecorder ? vocalRecorder.state != LinphoneEnums.RecorderStateClosed : false)
property bool isPlaying : vocalPlayer.item && vocalPlayer.item.playbackState === SoundPlayer.PlayingState
onVocalRecorderChanged: if(haveRecorder)
audioPreviewBlock.state = 'showed'
onIsRecordingChanged: if(isRecording) {
mediaProgressBar.start()
}else
mediaProgressBar.stop()
onIsPlayingChanged: isPlaying ? mediaProgressBar.resume() : mediaProgressBar.stop()
Layout.preferredHeight: 70
color: ChatAudioPreviewStyle.backgroundColor
radius: 0
state: "hidden"
visible: haveRecorder
onVisibleChanged: if(!visible) hide()
clip: false
function hide(){
state = 'hidden'
}
Loader {
id: vocalPlayer
active: false
sourceComponent: SoundPlayer {
source: (haveRecorder && vocalRecorder? vocalRecorder.file : '')
onStopped:{
mediaProgressBar.value = 100
}
Component.onCompleted: {
play()// This will open the file and allow seeking
pause()
}
}
}
RowLayout{
id: lineLayout
anchors.fill: parent
spacing: 10
ActionButton{
Layout.preferredHeight: iconSize
Layout.preferredWidth: iconSize
Layout.leftMargin: 10
Layout.alignment: Qt.AlignVCenter
isCustom: true
colorSet: ChatAudioPreviewStyle.deleteAction
onClicked: audioPreviewBlock.hide()
}
Item{
Layout.fillHeight: true
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
Layout.topMargin: 5
Layout.bottomMargin: 5
MediaProgressBar{
id: mediaProgressBar
anchors.fill: parent
progressDuration: 0
progressPosition: 0
stopAtEnd: !audioPreviewBlock.isRecording
resetAtEnd: false
colorSet: isRecording ? ChatAudioPreviewStyle.recordingProgressionWave : ChatAudioPreviewStyle.progressionWave
onEndReached:{
if(vocalPlayer.item)
vocalPlayer.item.stop()
}
onRefreshPositionRequested: if( vocalPlayer.item){
progressPosition = vocalPlayer.item.getPosition()
value = 100 * ( progressPosition / vocalPlayer.item.duration)
}else{// Recording
progressDuration = vocalRecorder.getDuration()
progressPosition = progressDuration
value = value + 0.01
}
onSeekRequested: if( vocalPlayer.item){
vocalPlayer.item.seek(ms)
progressPosition = vocalPlayer.item.getPosition()
value = 100 * (progressPosition / vocalPlayer.item.duration)
}
}
}
ActionButton{
Layout.preferredHeight: iconSize
Layout.preferredWidth: iconSize
Layout.rightMargin: 15
Layout.leftMargin: 5
Layout.alignment: Qt.AlignVCenter
isCustom: true
colorSet: audioPreviewBlock.isRecording ? ChatAudioPreviewStyle.stopAction
: (audioPreviewBlock.isPlaying ? ChatAudioPreviewStyle.pauseAction
: ChatAudioPreviewStyle.playAction)
onClicked:{
if(audioPreviewBlock.isRecording){// Stop the record and save the file
audioPreviewBlock.vocalRecorder.stop()
mediaProgressBar.value = 100
vocalPlayer.active = true
}else if(audioPreviewBlock.isPlaying){// Pause the play
vocalPlayer.item.pause()
}else{// Play the audio
vocalPlayer.item.play()
}
}
}
}
states: [
State {
name: "hidden"
PropertyChanges { target: audioPreviewBlock; opacity: 0 }
},
State {
name: "showed"
PropertyChanges { target: audioPreviewBlock; opacity: 1 }
}
]
transitions: [
Transition {
from: "*"; to: "showed"
SequentialAnimation{
ScriptAction{ script: audioPreviewBlock.vocalRecorder.start() }
NumberAnimation{ properties: "opacity"; easing.type: Easing.OutBounce; duration: 250 }
}
},
Transition {
from: "*"; to: "hidden"
SequentialAnimation{
ScriptAction{ script: RecorderManager.clearVocalRecorder()}
ScriptAction{ script: vocalPlayer.active = false }
NumberAnimation{ properties: "opacity"; duration: 250 }
}
}
]
}

View file

@ -49,7 +49,7 @@ Item {
id: headerText
height: icon.height
verticalAlignment: Qt.AlignVCenter
property string forwardInfo: mainChatMessageModel.getForwardInfoDisplayName
property string forwardInfo: mainChatMessageModel ? mainChatMessageModel.getForwardInfoDisplayName : ''
//: 'Forwarded' : Header on a message that contains a forward.
text: 'Forwarded' + (forwardInfo ? ' : ' +forwardInfo : '')
font.family: mainItem.customFont.family

View file

@ -15,18 +15,22 @@ import 'Chat.js' as Logic
// =============================================================================
ColumnLayout{
property alias replyChatMessageModel : replyPreview.replyChatMessageModel
property int maxHeight: parent.height
property int maxHeight: parent.height - ( audioPreview.visible ? audioPreview.height : 0)
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: replyPreview.height
spacing: 0
function hide(){
replyPreview.hide()
audioPreview.hide()
}
ChatReplyPreview{
id: replyPreview
Layout.fillWidth: true
}
ChatAudioPreview{
id: audioPreview
Layout.fillWidth: true
}
}

View file

@ -33,7 +33,7 @@ Item {
width: maxWidth < 0 || maxWidth > fitWidth ? fitWidth : maxWidth
height: fitHeight
onMainChatMessageModelChanged: if( mainChatMessageModel.replyChatMessageModel) chatMessageModel = mainChatMessageModel.replyChatMessageModel
onMainChatMessageModelChanged: if( mainChatMessageModel && mainChatMessageModel.replyChatMessageModel) chatMessageModel = mainChatMessageModel.replyChatMessageModel
ColumnLayout{
@ -118,7 +118,7 @@ Item {
font.pointSize: Units.dp * (customFont.pointSize + ChatReplyMessageStyle.replyArea.pointSizeOffset)
font.weight: Font.Light
color: ChatReplyMessageStyle.replyArea.foregroundColor
text: (visible ? Utils.encodeTextToQmlRichFormat(chatMessageModel.content, {
text: (visible && chatMessageModel? Utils.encodeTextToQmlRichFormat(chatMessageModel.content, {
imagesHeight: ChatStyle.entry.message.images.height,
imagesWidth: ChatStyle.entry.message.images.width
})

View file

@ -19,7 +19,7 @@ Rectangle{
property ChatMessageModel replyChatMessageModel
onReplyChatMessageModelChanged: if(replyChatMessageModel) replyPreviewBlock.state = "showed"
Layout.preferredHeight: Math.min(replayPreviewText.implicitHeight + replyPreviewHeaderArea.implicitHeight + 10, parent.maxHeight)
Layout.preferredHeight: Math.min(replyPreviewText.implicitHeight + replyPreviewHeaderArea.implicitHeight + 10, parent.maxHeight)
property int leftMargin: textArea.textLeftMargin
property int rightMargin: textArea.textRightMargin
@ -69,11 +69,11 @@ Rectangle{
}
}
Flickable {
id: replayPreviewTextArea
ScrollBar.vertical: ForceScrollBar {visible: replayPreviewTextArea.height < replayPreviewText.implicitHeight}
id: replyPreviewTextArea
ScrollBar.vertical: ForceScrollBar {visible: replyPreviewTextArea.height < replyPreviewText.implicitHeight}
boundsBehavior: Flickable.StopAtBounds
clip: true
contentHeight: replayPreviewText.implicitHeight
contentHeight: replyPreviewText.implicitHeight
contentWidth: width - ScrollBar.vertical.width
flickableDirection: Flickable.VerticalFlick
@ -81,7 +81,7 @@ Rectangle{
Layout.fillWidth: true
TextEdit {
id: replayPreviewText
id: replyPreviewText
property font customFont : SettingsModel.textMessageFont
anchors.left: parent.left

View file

@ -0,0 +1,120 @@
pragma Singleton
import QtQml 2.2
import Units 1.0
import ColorsList 1.0
// =============================================================================
QtObject {
property string sectionName : 'ChatAudioPreview'
property color color: ColorsList.add(sectionName, 'q').color
property QtObject header: QtObject{
property color color: ColorsList.add(sectionName+'_header', 'h').color
property int pointSizeOffset: -3
property QtObject replyIcon: QtObject{
property string icon : 'menu_reply_custom'
property int iconSize: 22
}
}
property color backgroundColor: ColorsList.add(sectionName+'_bg', 'aa').color
property QtObject audioArea: QtObject{
property color outgoingMarkColor: ColorsList.add(sectionName+'_reply_outgoing_mark', 'm').color
property color incomingMarkColor: ColorsList.add(sectionName+'_reply_incoming_mark', 'r').color
property color backgroundColor: ColorsList.add(sectionName+'_reply_bg', 'q').color
property color foregroundColor: ColorsList.add(sectionName+'_reply_fg', 'h').color
property int usernamePointSizeOffset: -2
property int pointSizeOffset: -2
}
property QtObject deleteAction: QtObject {
property int iconSize: 40
property string name : 'delete'
property string icon : 'delete_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_h_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'me_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_n_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_h_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'me_p_b_fg').color
}
property QtObject stopAction: QtObject {
property int iconSize: 30
property string name : 'stop'
property string icon : 'chat_audio_stop_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_h_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'me_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_n_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_h_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'me_p_b_fg').color
}
property QtObject pauseAction: QtObject {
property int iconSize: 30
property string name : 'pause'
property string icon : 'chat_audio_pause_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_h_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'me_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_n_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_h_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'me_p_b_fg').color
}
property QtObject playAction: QtObject {
property int iconSize: 30
property string name : 'play'
property string icon : 'chat_audio_play_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_h_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'me_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_n_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_h_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'me_p_b_fg').color
}
property QtObject progressionWave: QtObject{
property int iconSize: 30
property int iconHeight: 40
property int iconWidth: 250
property string name : 'progression_soundwave'
property string icon : 'chat_audio_soundwave_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'a_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'a_h_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'a_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'a_n_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'a_h_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'a_p_b_fg').color
property color backgroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_n', icon, 'l_n_b_bg').color
property color backgroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_h', icon, 'l_h_b_bg').color
property color backgroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_p', icon, 'l_p_b_bg').color
property color foregroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_n', icon, 'l_n_b_fg').color
property color foregroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_h', icon, 'l_h_b_fg').color
property color foregroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_p', icon, 'l_p_b_fg').color
}
property QtObject recordingProgressionWave: QtObject{
property int iconSize: 30
property int iconHeight: 40
property int iconWidth: 250
property string name : 'recording_progression_soundwave'
property string icon : 'chat_audio_soundwave_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'r_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'r_h_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'r_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'r_n_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'r_h_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'r_p_b_fg').color
property color backgroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_n', icon, 'l_n_b_bg').color
property color backgroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_h', icon, 'l_h_b_bg').color
property color backgroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_bg_p', icon, 'l_p_b_bg').color
property color foregroundHiddenPartNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_n', icon, 'l_n_b_fg').color
property color foregroundHiddenPartHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_h', icon, 'l_h_b_fg').color
property color foregroundHiddenPartPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_hidden_fg_p', icon, 'l_p_b_fg').color
}
property int padding: 8
}

View file

@ -10,6 +10,7 @@ singleton CardBlockStyle 1.0 Blocks/CardBlockStyle.qml
singleton RequestBlockStyle 1.0 Blocks/RequestBlockStyle.qml
singleton ChatStyle 1.0 Chat/ChatStyle.qml
singleton ChatAudioPreviewStyle 1.0 Chat/ChatAudioPreviewStyle.qml
singleton ChatForwardMessageStyle 1.0 Chat/ChatForwardMessageStyle.qml
singleton ChatReplyMessageStyle 1.0 Chat/ChatReplyMessageStyle.qml

View file

@ -15,6 +15,7 @@ Calls 1.0 Calls/Calls.qml
CallStatistics 1.0 Calls/CallStatistics.qml
Chat 1.0 Chat/Chat.qml
ChatAudioPreview 1.0 Chat/ChatAudioPreview.qml
ChatMessagePreview 1.0 Chat/ChatMessagePreview.qml
ChatForwardMessage 1.0 Chat/ChatForwardMessage.qml
ChatReplyMessage 1.0 Chat/ChatReplyMessage.qml

@ -1 +1 @@
Subproject commit 3ea9277c8035deb632e47519971122550ca8f64d
Subproject commit b1171bf5faf08237ddc6e074b71dd6319c0470af