Meeting invitation display in chat conversations

This commit is contained in:
Christophe Deschamps 2025-05-19 15:50:14 +00:00
parent ac7164fb0b
commit f8276ac834
13 changed files with 479 additions and 20 deletions

View file

@ -39,6 +39,8 @@ ChatMessageCore::ChatMessageCore(const std::shared_ptr<linphone::ChatMessage> &c
mChatMessageModel = Utils::makeQObject_ptr<ChatMessageModel>(chatmessage); mChatMessageModel = Utils::makeQObject_ptr<ChatMessageModel>(chatmessage);
mChatMessageModel->setSelf(mChatMessageModel); mChatMessageModel->setSelf(mChatMessageModel);
mText = mChatMessageModel->getText(); mText = mChatMessageModel->getText();
mUtf8Text = mChatMessageModel->getUtf8Text();
mHasTextContent = mChatMessageModel->getHasTextContent();
mTimestamp = QDateTime::fromSecsSinceEpoch(chatmessage->getTime()); mTimestamp = QDateTime::fromSecsSinceEpoch(chatmessage->getTime());
auto from = chatmessage->getFromAddress(); auto from = chatmessage->getFromAddress();
auto to = chatmessage->getLocalAddress(); auto to = chatmessage->getLocalAddress();
@ -56,6 +58,12 @@ ChatMessageCore::ChatMessageCore(const std::shared_ptr<linphone::ChatMessage> &c
mIsRead = chatmessage->isRead(); mIsRead = chatmessage->isRead();
mMessageState = LinphoneEnums::fromLinphone(chatmessage->getState()); mMessageState = LinphoneEnums::fromLinphone(chatmessage->getState());
mMessageId = Utils::coreStringToAppString(chatmessage->getMessageId()); mMessageId = Utils::coreStringToAppString(chatmessage->getMessageId());
for (auto content : chatmessage->getContents()) {
if (content->isIcalendar()) {
auto conferenceInfo = linphone::Factory::get()->createConferenceInfoFromIcalendarContent(content);
mConferenceInfo = ConferenceInfoCore::create(conferenceInfo);
}
}
} }
ChatMessageCore::~ChatMessageCore() { ChatMessageCore::~ChatMessageCore() {
@ -165,3 +173,7 @@ void ChatMessageCore::setMessageState(LinphoneEnums::ChatMessageState state) {
std::shared_ptr<ChatMessageModel> ChatMessageCore::getModel() const { std::shared_ptr<ChatMessageModel> ChatMessageCore::getModel() const {
return mChatMessageModel; return mChatMessageModel;
} }
ConferenceInfoGui *ChatMessageCore::getConferenceInfoGui() const {
return mConferenceInfo ? new ConferenceInfoGui(mConferenceInfo) : nullptr;
}

View file

@ -21,6 +21,8 @@
#ifndef CHATMESSAGECORE_H_ #ifndef CHATMESSAGECORE_H_
#define CHATMESSAGECORE_H_ #define CHATMESSAGECORE_H_
#include "core/conference/ConferenceInfoCore.hpp"
#include "core/conference/ConferenceInfoGui.hpp"
#include "model/chat/message/ChatMessageModel.hpp" #include "model/chat/message/ChatMessageModel.hpp"
#include "tool/AbstractObject.hpp" #include "tool/AbstractObject.hpp"
#include "tool/thread/SafeConnection.hpp" #include "tool/thread/SafeConnection.hpp"
@ -35,6 +37,8 @@ class ChatMessageCore : public QObject, public AbstractObject {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QDateTime timestamp READ getTimestamp WRITE setTimestamp NOTIFY timestampChanged) Q_PROPERTY(QDateTime timestamp READ getTimestamp WRITE setTimestamp NOTIFY timestampChanged)
Q_PROPERTY(QString text READ getText WRITE setText NOTIFY textChanged) Q_PROPERTY(QString text READ getText WRITE setText NOTIFY textChanged)
Q_PROPERTY(QString utf8Text MEMBER mUtf8Text CONSTANT)
Q_PROPERTY(bool hasTextContent MEMBER mHasTextContent CONSTANT)
Q_PROPERTY(QString peerAddress READ getPeerAddress CONSTANT) Q_PROPERTY(QString peerAddress READ getPeerAddress CONSTANT)
Q_PROPERTY(QString fromAddress READ getFromAddress CONSTANT) Q_PROPERTY(QString fromAddress READ getFromAddress CONSTANT)
Q_PROPERTY(QString toAddress READ getToAddress CONSTANT) Q_PROPERTY(QString toAddress READ getToAddress CONSTANT)
@ -45,6 +49,7 @@ class ChatMessageCore : public QObject, public AbstractObject {
Q_PROPERTY(bool isRemoteMessage READ isRemoteMessage CONSTANT) Q_PROPERTY(bool isRemoteMessage READ isRemoteMessage CONSTANT)
Q_PROPERTY(bool isFromChatGroup READ isFromChatGroup CONSTANT) Q_PROPERTY(bool isFromChatGroup READ isFromChatGroup CONSTANT)
Q_PROPERTY(bool isRead READ isRead WRITE setIsRead NOTIFY isReadChanged) Q_PROPERTY(bool isRead READ isRead WRITE setIsRead NOTIFY isReadChanged)
Q_PROPERTY(ConferenceInfoGui *conferenceInfo READ getConferenceInfoGui CONSTANT)
public: public:
static QSharedPointer<ChatMessageCore> create(const std::shared_ptr<linphone::ChatMessage> &chatmessage); static QSharedPointer<ChatMessageCore> create(const std::shared_ptr<linphone::ChatMessage> &chatmessage);
@ -75,10 +80,12 @@ public:
void setMessageState(LinphoneEnums::ChatMessageState state); void setMessageState(LinphoneEnums::ChatMessageState state);
std::shared_ptr<ChatMessageModel> getModel() const; std::shared_ptr<ChatMessageModel> getModel() const;
ConferenceInfoGui *getConferenceInfoGui() const;
signals: signals:
void timestampChanged(QDateTime timestamp); void timestampChanged(QDateTime timestamp);
void textChanged(QString text); void textChanged(QString text);
void utf8TextChanged(QString text);
void isReadChanged(bool read); void isReadChanged(bool read);
void isRemoteMessageChanged(bool isRemote); void isRemoteMessageChanged(bool isRemote);
void messageStateChanged(); void messageStateChanged();
@ -90,6 +97,8 @@ signals:
private: private:
DECLARE_ABSTRACT_OBJECT QString mText; DECLARE_ABSTRACT_OBJECT QString mText;
QString mUtf8Text;
bool mHasTextContent;
QString mPeerAddress; QString mPeerAddress;
QString mFromAddress; QString mFromAddress;
QString mToAddress; QString mToAddress;
@ -101,6 +110,7 @@ private:
bool mIsFromChatGroup = false; bool mIsFromChatGroup = false;
bool mIsRead = false; bool mIsRead = false;
LinphoneEnums::ChatMessageState mMessageState; LinphoneEnums::ChatMessageState mMessageState;
QSharedPointer<ConferenceInfoCore> mConferenceInfo = nullptr;
std::shared_ptr<ChatMessageModel> mChatMessageModel; std::shared_ptr<ChatMessageModel> mChatMessageModel;
QSharedPointer<SafeConnection<ChatMessageCore, ChatMessageModel>> mChatMessageModelConnection; QSharedPointer<SafeConnection<ChatMessageCore, ChatMessageModel>> mChatMessageModelConnection;

View file

@ -1,4 +1,4 @@
/* /*
* Copyright (c) 2021 Belledonne Communications SARL. * Copyright (c) 2021 Belledonne Communications SARL.
* *
* This file is part of linphone-desktop * This file is part of linphone-desktop
@ -74,11 +74,7 @@ ConferenceInfoCore::ConferenceInfoCore(std::shared_ptr<linphone::ConferenceInfo>
mEndDateTime = mDateTime.addSecs(mDuration * 60); mEndDateTime = mDateTime.addSecs(mDuration * 60);
mIsScheduled = mDateTime.isValid(); mIsScheduled = mDateTime.isValid();
mOrganizerAddress = Utils::coreStringToAppString(conferenceInfo->getOrganizer()->asStringUriOnly()); mOrganizerAddress = Utils::coreStringToAppString(conferenceInfo->getOrganizer()->asStringUriOnly());
mOrganizerName = Utils::coreStringToAppString(conferenceInfo->getOrganizer()->getDisplayName()); mOrganizerName = mConferenceInfoModel->getOrganizerName();
if (mOrganizerName.isEmpty()) {
mOrganizerName = Utils::coreStringToAppString(conferenceInfo->getOrganizer()->getUsername());
mOrganizerName.replace(".", " ");
}
mSubject = Utils::coreStringToAppString(conferenceInfo->getSubject()); mSubject = Utils::coreStringToAppString(conferenceInfo->getSubject());
mDescription = Utils::coreStringToAppString(conferenceInfo->getDescription()); mDescription = Utils::coreStringToAppString(conferenceInfo->getDescription());
mIsEnded = getDateTimeUtc().addSecs(mDuration * 60) < QDateTime::currentDateTimeUtc(); mIsEnded = getDateTimeUtc().addSecs(mDuration * 60) < QDateTime::currentDateTimeUtc();

View file

@ -4316,6 +4316,24 @@ To enable them in a commercial project, please contact us.</translation>
<source>unknown_audio_device_name</source> <source>unknown_audio_device_name</source>
<translation>Unknown device name</translation> <translation>Unknown device name</translation>
</message> </message>
<message>
<location filename="../../model/chat/message/ChatMessageModel.cpp"/>
<source>conference_invitation</source>
<extracomment>&quot;Invitation à une réunion;&quot;</extracomment>
<translation>Meeting invitation</translation>
</message>
<message>
<location filename="../../model/chat/message/ChatMessageModel.cpp"/>
<source>conference_invitation_cancelled</source>
<extracomment>&quot;Annulation d'une réunion;&quot;</extracomment>
<translation>Meeting cancellation</translation>
</message>
<message>
<location filename="../../model/chat/message/ChatMessageModel.cpp"/>
<source>conference_invitation_updated</source>
<extracomment>&quot;Modification d'une réunion;&quot;</extracomment>
<translation>Meeting modification</translation>
</message>
</context> </context>
<context> <context>
<name>Utils</name> <name>Utils</name>
@ -5831,4 +5849,55 @@ Failed to create 1-1 conversation with %1 !</extracomment>
<translation>Ok</translation> <translation>Ok</translation>
</message> </message>
</context> </context>
<context>
<name>ChatMessageInvitationBubble</name>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessageInvitationBubble.qml"/>
<source>ics_bubble_organiser_invites_you_to</source>
<extracomment>&quot; vous invite à :;&quot;</extracomment>
<translation> invites you to :</translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessageInvitationBubble.qml"/>
<source>ics_bubble_organiser_modified</source>
<extracomment>&quot; a modifié :;&quot;</extracomment>
<translation> modified :</translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessageInvitationBubble.qml"/>
<source>ics_bubble_organiser_cancelled</source>
<extracomment>&quot; a annulé :;&quot;</extracomment>
<translation> cancelled :</translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessageInvitationBubble.qml"/>
<source>ics_bubble_meeting_from</source>
<extracomment>&quot;de ;&quot;</extracomment>
<translation>from </translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessageInvitationBubble.qml"/>
<source>ics_bubble_meeting_to</source>
<extracomment>&quot; à ;&quot;</extracomment>
<translation> to </translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessageInvitationBubble.qml"/>
<source>ics_bubble_description_title</source>
<extracomment>&quot;Description;&quot;</extracomment>
<translation>Description</translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessageInvitationBubble.qml"/>
<source>ics_bubble_join</source>
<extracomment>&quot;Rejoindre;&quot;</extracomment>
<translation>Join</translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessageInvitationBubble.qml"/>
<source>ics_bubble_participants</source>
<extracomment>&quot; participants;&quot;</extracomment>
<translation> participants</translation>
</message>
</context>
</TS> </TS>

View file

@ -4331,6 +4331,24 @@ Pour les activer dans un projet commercial, merci de nous contacter.</translatio
<source>unknown_audio_device_name</source> <source>unknown_audio_device_name</source>
<translation>Appareil inconnu</translation> <translation>Appareil inconnu</translation>
</message> </message>
<message>
<location filename="../../model/chat/message/ChatMessageModel.cpp"/>
<source>conference_invitation</source>
<extracomment>&quot;Invitation à une réunion;&quot;</extracomment>
<translation>Invitation à une réunion</translation>
</message>
<message>
<location filename="../../model/chat/message/ChatMessageModel.cpp"/>
<source>conference_invitation_cancelled</source>
<extracomment>&quot;Annulation d'une réunion;&quot;</extracomment>
<translation>Annulation d'une réunion</translation>
</message>
<message>
<location filename="../../model/chat/message/ChatMessageModel.cpp"/>
<source>conference_invitation_updated</source>
<extracomment>&quot;Modification d'une réunion;&quot;</extracomment>
<translation>Modification d'une réunion</translation>
</message>
</context> </context>
<context> <context>
<name>Utils</name> <name>Utils</name>
@ -5846,4 +5864,55 @@ Failed to create 1-1 conversation with %1 !</extracomment>
<translation>Ok</translation> <translation>Ok</translation>
</message> </message>
</context> </context>
<context>
<name>ChatMessageInvitationBubble</name>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessageInvitationBubble.qml"/>
<source>ics_bubble_organiser_invites_you_to</source>
<extracomment>&quot; vous invite à :&quot;</extracomment>
<translation> vous invite à :</translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessageInvitationBubble.qml"/>
<source>ics_bubble_organiser_modified</source>
<extracomment>&quot; a modifié :;&quot;</extracomment>
<translation> a modifié :</translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessageInvitationBubble.qml"/>
<source>ics_bubble_organiser_cancelled</source>
<extracomment>&quot; a annulé :;&quot;</extracomment>
<translation> a annulé :</translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessageInvitationBubble.qml"/>
<source>ics_bubble_meeting_from</source>
<extracomment>&quot;de ;&quot;</extracomment>
<translation>de </translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessageInvitationBubble.qml"/>
<source>ics_bubble_meeting_to</source>
<extracomment>&quot; à ;&quot;</extracomment>
<translation> à </translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessageInvitationBubble.qml"/>
<source>ics_bubble_description_title</source>
<extracomment>&quot;Description;&quot;</extracomment>
<translation>Description</translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessageInvitationBubble.qml"/>
<source>ics_bubble_join</source>
<extracomment>&quot;Rejoindre;&quot;</extracomment>
<translation>Rejoindre</translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessageInvitationBubble.qml"/>
<source>ics_bubble_participants</source>
<extracomment>&quot; participants;&quot;</extracomment>
<translation> participants</translation>
</message>
</context>
</TS> </TS>

View file

@ -44,6 +44,17 @@ QString ChatMessageModel::getText() const {
return ToolModel::getMessageFromContent(mMonitor->getContents()); return ToolModel::getMessageFromContent(mMonitor->getContents());
} }
QString ChatMessageModel::getUtf8Text() const {
return Utils::coreStringToAppString(mMonitor->getUtf8Text());
}
bool ChatMessageModel::getHasTextContent() const {
for (auto content : mMonitor->getContents()) {
if (content->isText()) return true;
}
return false;
}
QString ChatMessageModel::getPeerAddress() const { QString ChatMessageModel::getPeerAddress() const {
return Utils::coreStringToAppString(mMonitor->getPeerAddress()->asStringUriOnly()); return Utils::coreStringToAppString(mMonitor->getPeerAddress()->asStringUriOnly());
} }

View file

@ -38,6 +38,9 @@ public:
~ChatMessageModel(); ~ChatMessageModel();
QString getText() const; QString getText() const;
QString getUtf8Text() const;
bool getHasTextContent() const;
QDateTime getTimestamp() const; QDateTime getTimestamp() const;
QString getPeerAddress() const; QString getPeerAddress() const;

View file

@ -111,9 +111,7 @@ linphone::ConferenceInfo::State ConferenceInfoModel::getState() const {
QString ConferenceInfoModel::getOrganizerName() const { QString ConferenceInfoModel::getOrganizerName() const {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
auto organizer = mConferenceInfo->getOrganizer()->clone(); auto organizer = mConferenceInfo->getOrganizer()->clone();
auto name = Utils::coreStringToAppString(organizer->getDisplayName()); return ToolModel::getDisplayName(organizer);
if (name.isEmpty()) name = ToolModel::getDisplayName(organizer);
return name;
} }
QString ConferenceInfoModel::getOrganizerAddress() const { QString ConferenceInfoModel::getOrganizerAddress() const {

View file

@ -28,6 +28,7 @@
#include <QDirIterator> #include <QDirIterator>
#include <QLibrary> #include <QLibrary>
#include <QTest> #include <QTest>
#include "core/conference/ConferenceInfoCore.hpp"
DEFINE_ABSTRACT_OBJECT(ToolModel) DEFINE_ABSTRACT_OBJECT(ToolModel)
@ -385,13 +386,21 @@ bool ToolModel::friendIsInFriendList(const std::shared_ptr<linphone::FriendList>
} }
QString ToolModel::getMessageFromContent(std::list<std::shared_ptr<linphone::Content>> contents) { QString ToolModel::getMessageFromContent(std::list<std::shared_ptr<linphone::Content>> contents) {
mustBeInLinphoneThread(sLog().arg(Q_FUNC_INFO));
for (auto &content : contents) { for (auto &content : contents) {
if (content->isText()) { if (content->isText()) {
return Utils::coreStringToAppString(content->getUtf8Text()); return Utils::coreStringToAppString(content->getUtf8Text());
} else if (content->isFile()) { } else if (content->isFile()) {
return Utils::coreStringToAppString(content->getName()); return Utils::coreStringToAppString(content->getName());
} else if (content->isIcalendar()) { } else if (content->isIcalendar()) {
return QString("Invitation à une réunion"); auto conferenceInfo = linphone::Factory::get()->createConferenceInfoFromIcalendarContent(content);
auto conferenceInfoCore = ConferenceInfoCore::create(conferenceInfo);
if (conferenceInfoCore->getConferenceInfoState() == LinphoneEnums::ConferenceInfoState::New)
return tr("conference_invitation");
if (conferenceInfoCore->getConferenceInfoState() == LinphoneEnums::ConferenceInfoState::Updated)
return tr("conference_invitation_updated");
if (conferenceInfoCore->getConferenceInfoState() == LinphoneEnums::ConferenceInfoState::Cancelled)
return tr("conference_invitation_cancelled");
} else if (content->isMultipart()) { } else if (content->isMultipart()) {
return getMessageFromContent(content->getParts()); return getMessageFromContent(content->getParts());
} }

View file

@ -54,6 +54,7 @@ list(APPEND _LINPHONEAPP_QML_FILES
view/Control/Display/Call/CallStatistics.qml view/Control/Display/Call/CallStatistics.qml
view/Control/Display/Chat/ChatListView.qml view/Control/Display/Chat/ChatListView.qml
view/Control/Display/Chat/ChatMessage.qml view/Control/Display/Chat/ChatMessage.qml
view/Control/Display/Chat/ChatMessageInvitationBubble.qml
view/Control/Display/Chat/ChatMessagesListView.qml view/Control/Display/Chat/ChatMessagesListView.qml
view/Control/Display/Contact/Avatar.qml view/Control/Display/Contact/Avatar.qml
view/Control/Display/Contact/Contact.qml view/Control/Display/Contact/Contact.qml

View file

@ -20,7 +20,7 @@ Control.Control {
property bool isRemoteMessage: chatMessage? chatMessage.core.isRemoteMessage : false property bool isRemoteMessage: chatMessage? chatMessage.core.isRemoteMessage : false
property bool isFromChatGroup: chatMessage? chatMessage.core.isFromChatGroup : false property bool isFromChatGroup: chatMessage? chatMessage.core.isFromChatGroup : false
property var msgState: chatMessage ? chatMessage.core.messageState : LinphoneEnums.ChatMessageState.StateIdle property var msgState: chatMessage ? chatMessage.core.messageState : LinphoneEnums.ChatMessageState.StateIdle
property string richFormatText: UtilsCpp.encodeTextToQmlRichFormat(modelData.core.text) property string richFormatText: modelData.core.hasTextContent ? UtilsCpp.encodeTextToQmlRichFormat(modelData.core.utf8Text) : ""
hoverEnabled: true hoverEnabled: true
property bool linkHovered: false property bool linkHovered: false
@ -43,6 +43,12 @@ Control.Control {
} }
} }
} }
function handleDefaultMouseEvent(event) {
if (event.button === Qt.RightButton) {
optionsMenu.open()
}
}
contentItem: RowLayout { contentItem: RowLayout {
spacing: 0 spacing: 0
@ -69,15 +75,13 @@ Control.Control {
leftPadding: Math.round(12 * DefaultStyle.dp) leftPadding: Math.round(12 * DefaultStyle.dp)
rightPadding: Math.round(12 * DefaultStyle.dp) rightPadding: Math.round(12 * DefaultStyle.dp)
MouseArea { MouseArea { // Default mouse area. Each sub bubble can control the mouse and pass on to the main mouse handler. Child bubble mouse area must cover the entire bubble.
id: defaultMouseArea
visible: invitationLoader.status !== Loader.Ready // Add other bubbles here that could control the mouse themselves, then add in bubble a signal onMouseEvent
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.RightButton acceptedButtons: Qt.RightButton
onClicked: (mouse) => { onClicked: (mouse) => mainItem.handleDefaultMouseEvent(mouse)
if (mouse.button === Qt.RightButton) { cursorShape: mainItem.linkHovered ? Qt.PointingHandCursor : Qt.ArrowCursor
optionsMenu.open()
}
}
cursorShape: mainItem.linkHovered ? Qt.PointingHandCursor : Qt.IBeamCursor
} }
background: Item { background: Item {
@ -110,7 +114,7 @@ Control.Control {
visible: mainItem.imgUrl != undefined visible: mainItem.imgUrl != undefined
id: contentimage id: contentimage
} }
Text { Text { // Uses default mouse area for link hovering.
id: textElement id: textElement
visible: mainItem.richFormatText !== "" visible: mainItem.richFormatText !== ""
text: mainItem.richFormatText text: mainItem.richFormatText
@ -134,6 +138,25 @@ Control.Control {
mainItem.linkHovered = hoveredLink !== "" mainItem.linkHovered = hoveredLink !== ""
} }
} }
// Meeting invitation bubble
/////////////////////////////
Loader {
id: invitationLoader
active: modelData.core.conferenceInfo !== null
sourceComponent: invitationComponent
}
Component {
id: invitationComponent
ChatMessageInvitationBubble {
conferenceInfoGui: modelData.core.conferenceInfo
Layout.fillWidth: true
Layout.fillHeight: true
onMouseEvent: mainItem.handleDefaultMouseEvent(event)
}
}
/////////////////////////////
RowLayout { RowLayout {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Text { Text {

View file

@ -0,0 +1,258 @@
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import QtQuick.Controls.Basic as Control
import Linphone
import UtilsCpp
import SettingsCpp
import "qrc:/qt/qml/Linphone/view/Style/buttonStyle.js" as ButtonStyle
import "qrc:/qt/qml/Linphone/view/Control/Tool/Helper/utils.js" as Utils
Rectangle {
id: mainItem
width: 490 * DefaultStyle.dp
height: content.implicitHeight
radius: 10 * DefaultStyle.dp
clip: true
antialiasing: true
property var conferenceInfoGui: ConferenceInfoGui
property var conferenceInfo: conferenceInfoGui?.core
property string timeRangeText: ""
property bool linkHovered: false
signal mouseEvent(MouseEvent event)
MouseArea {
anchors.fill: parent
cursorShape: mainItem.linkHovered ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: (mouse) => mouseEvent(mouse)
acceptedButtons: Qt.AllButtons // Send all to parent
}
function updateTimeRange() {
if (!conferenceInfo || !conferenceInfo.dateTime || !conferenceInfo.duration)
return;
let locale = Qt.locale();
let startDate = conferenceInfo.dateTime;
let endDate = new Date(startDate.getTime() + (conferenceInfo.duration * 60 * 1000));
let startTime = startDate.toLocaleTimeString(locale, "hh:mm");
let endTime = endDate.toLocaleTimeString(locale, "hh:mm");
let offsetMinutes = -startDate.getTimezoneOffset();
let offsetHours = Math.floor(offsetMinutes / 60);
let timeZone = "UTC" + (offsetHours >= 0 ? "+" : "") + offsetHours;
timeRangeText =
qsTr("ics_bubble_meeting_from") + startTime +
qsTr("ics_bubble_meeting_to") + endTime + " (" + timeZone + ")";
}
ColumnLayout {
id: content
anchors.fill: parent
spacing: 0
Rectangle {
Layout.fillWidth: true
height: row1.implicitHeight + 32 * DefaultStyle.dp
color: DefaultStyle.grey_100
radius: 10 * DefaultStyle.dp // rounded all, but only top visible
ColumnLayout {
id: row1
anchors.fill: parent
anchors.margins: 16 * DefaultStyle.dp
Text {
text: conferenceInfo.organizerName + (
conferenceInfo.state == LinphoneEnums.ConferenceInfoState.New ?
qsTr("ics_bubble_organiser_invites_you_to") :
conferenceInfo.state == LinphoneEnums.ConferenceInfoState.Updated ?
qsTr("ics_bubble_organiser_modified") :
conferenceInfo.state == LinphoneEnums.ConferenceInfoState.Cancelled ?
qsTr("ics_bubble_organiser_cancelled") :
""
)
font: Typography.p2
color: conferenceInfo.state == LinphoneEnums.ConferenceInfoState.New ?
DefaultStyle.main2_600 :
conferenceInfo.state == LinphoneEnums.ConferenceInfoState.Updated ?
DefaultStyle.warning_600 :
conferenceInfo.state == LinphoneEnums.ConferenceInfoState.Cancelled ?
DefaultStyle.danger_500main :
DefaultStyle.main2_600
}
RowLayout {
Layout.fillWidth: true
spacing: 10 * DefaultStyle.dp
Rectangle {
width: 48 * DefaultStyle.dp
height: 48 * DefaultStyle.dp
radius: 10 * DefaultStyle.dp
color: "transparent"
Rectangle {
anchors.fill: parent
color: "#33000000"
radius: 10 * DefaultStyle.dp
anchors.verticalCenterOffset: 4 * DefaultStyle.dp
z: -1
}
Rectangle {
id: dayRect
anchors.fill: parent
radius: 10 * DefaultStyle.dp
color: DefaultStyle.grey_0
Column {
anchors.centerIn: parent
spacing: 2 * DefaultStyle.dp
Text {
text: conferenceInfo.dateTime.toLocaleString(Qt.locale(), "ddd") + "."
color: DefaultStyle.main2_500main
font: Typography.p4
horizontalAlignment: Text.AlignHCenter
anchors.horizontalCenter: parent.horizontalCenter
}
Rectangle {
width: 23 * DefaultStyle.dp
height: 23 * DefaultStyle.dp
radius: width / 2
color: DefaultStyle.main1_500_main
anchors.horizontalCenter: parent.horizontalCenter
Text {
text: conferenceInfo.dateTime.getDate().toString()
color: DefaultStyle.grey_0
font: Typography.h4
anchors.centerIn: parent
}
}
}
}
}
// Info
ColumnLayout {
RowLayout {
EffectImage {
imageSource: AppIcons.usersThree
colorizationColor: DefaultStyle.main2_600
Layout.preferredWidth: Math.round(24 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(24 * DefaultStyle.dp)
}
Text {
text: conferenceInfo.subject
font: Typography.p2
wrapMode: Text.WordWrap
color: DefaultStyle.main2_600
}
}
Text {
text: conferenceInfo.dateTime.toLocaleString(Qt.locale(), "dddd d MMMM yyyy")
font: Typography.p4
color: DefaultStyle.main2_500main
}
Text {
text: timeRangeText
font: Typography.p4
color: DefaultStyle.main2_500main
}
}
}
}
}
Rectangle {
Layout.fillWidth: true
height: 10 * DefaultStyle.dp
color: DefaultStyle.grey_100
Layout.topMargin: -10 * DefaultStyle.dp
}
Rectangle {
Layout.fillWidth: true
height: row2.implicitHeight + 32 * DefaultStyle.dp
color: DefaultStyle.grey_0
radius: 10 * DefaultStyle.dp
ColumnLayout {
id: row2
spacing: 10 * DefaultStyle.dp
anchors.fill: parent
anchors.margins: 16 * DefaultStyle.dp
Text {
text: qsTr("ics_bubble_description_title")
font: Typography.p4
color: DefaultStyle.main2_800
visible: conferenceInfo.description.length > 0
}
Text {
text: UtilsCpp.encodeTextToQmlRichFormat(conferenceInfo.description)
wrapMode: Text.WordWrap
textFormat: Text.RichText
font: Typography.p4
color: DefaultStyle.main2_500main
visible: conferenceInfo.description.length > 0
onLinkActivated: {
if (link.startsWith('sip'))
UtilsCpp.createCall(link)
else
Qt.openUrlExternally(link)
}
onHoveredLinkChanged: {
mainItem.linkHovered = hoveredLink !== ""
}
}
RowLayout {
Layout.fillHeight: true
Layout.preferredHeight: 30 * DefaultStyle.dp
spacing: 10 * DefaultStyle.dp
EffectImage {
imageSource: AppIcons.usersTwo
colorizationColor: DefaultStyle.main2_600
Layout.preferredWidth: Math.round(14 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(14 * DefaultStyle.dp)
}
Text {
text: conferenceInfo.participantCount + qsTr("ics_bubble_participants")
font: Typography.p4
color: DefaultStyle.main2_800
}
Item {
Layout.fillWidth: true
}
MediumButton {
style: ButtonStyle.ButtonStyle
//: "Rejoindre"
text: qsTr("ics_bubble_join")
visible: !SettingsCpp.disableMeetingsFeature && conferenceInfo.state != LinphoneEnums.ConferenceInfoState.Cancelled
onClicked: {
var callsWindow = UtilsCpp.getCallsWindow()
callsWindow.setupConference(mainItem.conferenceInfoGui)
UtilsCpp.smartShowWindow(callsWindow)
}
}
}
Item {
Layout.fillHeight: true
}
}
}
}
}

View file

@ -23,7 +23,7 @@ QtObject {
pixelSize: Math.round(20 * DefaultStyle.dp), pixelSize: Math.round(20 * DefaultStyle.dp),
weight: Math.min(Math.round(800 * DefaultStyle.dp), 1000) weight: Math.min(Math.round(800 * DefaultStyle.dp), 1000)
}) })
// Title/H2 - Large bloc title // Title/H2 - Large bloc title
property font h2: Qt.font( { property font h2: Qt.font( {
family: DefaultStyle.defaultFont, family: DefaultStyle.defaultFont,