create new chat

This commit is contained in:
Gaelle Braud 2025-05-15 17:31:38 +02:00
parent 5808368b9a
commit 75e71be14d
24 changed files with 593 additions and 331 deletions

View file

@ -89,6 +89,7 @@ ChatCore::ChatCore(const std::shared_ptr<linphone::ChatRoom> &chatRoom) : QObjec
}
resetChatMessageList(messageList);
mIdentifier = Utils::coreStringToAppString(chatRoom->getIdentifier());
mChatRoomState = LinphoneEnums::fromLinphone(chatRoom->getState());
connect(this, &ChatCore::messageListChanged, this, &ChatCore::lUpdateLastMessage);
connect(this, &ChatCore::messagesInserted, this, &ChatCore::lUpdateLastMessage);
connect(this, &ChatCore::messageRemoved, this, &ChatCore::lUpdateLastMessage);
@ -132,6 +133,12 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
});
mChatModelConnection->makeConnectToModel(
&ChatModel::deleted, [this]() { mChatModelConnection->invokeToCore([this]() { emit deleted(); }); });
mChatModelConnection->makeConnectToModel(
&ChatModel::stateChanged,
[this](const std::shared_ptr<linphone::ChatRoom> &chatRoom, linphone::ChatRoom::State newState) {
auto state = LinphoneEnums::fromLinphone(newState);
mChatModelConnection->invokeToCore([this, state]() { setChatRoomState(state); });
});
mChatModelConnection->makeConnectToModel(&ChatModel::chatMessageReceived,
[this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
@ -277,6 +284,17 @@ LinphoneEnums::ChatMessageState ChatCore::getLastMessageState() const {
return mLastMessage ? mLastMessage->getMessageState() : LinphoneEnums::ChatMessageState::StateIdle;
}
LinphoneEnums::ChatRoomState ChatCore::getChatRoomState() const {
return mChatRoomState;
}
void ChatCore::setChatRoomState(LinphoneEnums::ChatRoomState state) {
if (mChatRoomState != state) {
mChatRoomState = state;
emit chatRoomStateChanged();
}
}
ChatMessageGui *ChatCore::getLastMessage() const {
return mLastMessage ? new ChatMessageGui(mLastMessage) : nullptr;
}

View file

@ -43,6 +43,7 @@ public:
Q_PROPERTY(QString lastMessageText READ getLastMessageText NOTIFY lastMessageChanged)
Q_PROPERTY(ChatMessageGui *lastMessage READ getLastMessage NOTIFY lastMessageChanged)
Q_PROPERTY(LinphoneEnums::ChatMessageState lastMessageState READ getLastMessageState NOTIFY lastMessageChanged)
Q_PROPERTY(LinphoneEnums::ChatRoomState state READ getChatRoomState NOTIFY chatRoomStateChanged)
Q_PROPERTY(int unreadMessagesCount READ getUnreadMessagesCount WRITE setUnreadMessagesCount NOTIFY
unreadMessagesCountChanged)
Q_PROPERTY(QString composingName READ getComposingName WRITE setComposingName NOTIFY composingUserChanged)
@ -70,6 +71,9 @@ public:
LinphoneEnums::ChatMessageState getLastMessageState() const;
LinphoneEnums::ChatRoomState getChatRoomState() const;
void setChatRoomState(LinphoneEnums::ChatRoomState state);
QSharedPointer<ChatMessageCore> getLastMessageCore() const;
void setLastMessage(QSharedPointer<ChatMessageCore> lastMessage);
@ -96,11 +100,9 @@ public:
std::shared_ptr<ChatModel> getModel() const;
Q_SIGNALS:
signals:
// used to close all the notifications when one is clicked
void messageOpen();
signals:
void lastUpdatedTimeChanged(QDateTime time);
void lastMessageChanged();
void titleChanged(QString title);
@ -111,6 +113,7 @@ signals:
void avatarUriChanged();
void deleted();
void composingUserChanged();
void chatRoomStateChanged();
void lDeleteMessage();
void lDelete();
@ -134,6 +137,7 @@ private:
QString mComposingName;
QString mComposingAddress;
bool mIsGroupChat = false;
LinphoneEnums::ChatRoomState mChatRoomState;
std::shared_ptr<ChatModel> mChatModel;
QSharedPointer<ChatMessageCore> mLastMessage;
QList<QSharedPointer<ChatMessageCore>> mChatMessageList;

View file

@ -103,28 +103,6 @@ void ChatList::setSelf(QSharedPointer<ChatList> me) {
});
});
mModelConnection->makeConnectToModel(
&CoreModel::chatRoomStateChanged,
[this](const std::shared_ptr<linphone::Core> &core, const std::shared_ptr<linphone::ChatRoom> &chatRoom,
linphone::ChatRoom::State state) {
// check account, filter, then add if ok
if (chatRoom->getAccount() == core->getDefaultAccount()) {
if (state == linphone::ChatRoom::State::Created) {
auto list = getSharedList<ChatCore>();
auto found =
std::find_if(list.begin(), list.end(), [chatRoom](const QSharedPointer<ChatCore> &item) {
return (item && item->getModel()->getMonitor() == chatRoom);
});
if (found == list.end()) {
auto model = createChatCore(chatRoom);
mModelConnection->invokeToCore([this, model]() {
add(model);
emit chatAdded();
});
}
}
}
});
mModelConnection->makeConnectToModel(
&CoreModel::defaultAccountChanged,
[this](std::shared_ptr<linphone::Core> core, std::shared_ptr<linphone::Account> account) { lUpdate(); });
@ -146,6 +124,19 @@ int ChatList::findChatIndex(ChatGui *chatGui) {
return it == chatList.end() ? -1 : std::distance(chatList.begin(), it);
}
void ChatList::addChatInList(ChatGui *chatGui) {
auto chatCore = chatGui->mCore;
auto chatList = getSharedList<ChatCore>();
auto it = std::find_if(chatList.begin(), chatList.end(), [chatCore](const QSharedPointer<ChatCore> item) {
return item->getIdentifier() == chatCore->getIdentifier();
});
if (it == chatList.end()) {
connectItem(chatCore);
add(chatCore);
emit chatAdded();
}
}
QVariant ChatList::data(const QModelIndex &index, int role) const {
int row = index.row();
if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant();

View file

@ -42,6 +42,7 @@ public:
void connectItem(QSharedPointer<ChatCore> chat);
int findChatIndex(ChatGui *chat);
void addChatInList(ChatGui *chatGui);
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
signals:

View file

@ -66,6 +66,13 @@ int ChatProxy::findChatIndex(ChatGui *chatGui) {
return -1;
}
void ChatProxy::addChatInList(ChatGui *chatGui) {
auto chatList = getListModel<ChatList>();
if (chatList) {
chatList->addChatInList(chatGui);
}
}
bool ChatProxy::SortFilterList::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const {
// auto l = getItemAtSource<ChatList, ChatCore>(sourceRow);
// return l != nullptr;

View file

@ -40,6 +40,7 @@ public:
void setSourceModel(QAbstractItemModel *sourceModel) override;
Q_INVOKABLE int findChatIndex(ChatGui *chatGui);
Q_INVOKABLE void addChatInList(ChatGui *chatGui);
signals:
void chatRemoved(ChatGui *chat);

View file

@ -467,11 +467,19 @@ QString ToolModel::computeUserAgent(const std::shared_ptr<linphone::Config> &con
.remove("'");
}
bool ToolModel::isEndToEndEncryptedChatAvailable() {
auto core = CoreModel::getInstance()->getCore();
auto defaultAccount = core->getDefaultAccount();
return core->limeX3DhEnabled() && defaultAccount && !defaultAccount->getParams()->getLimeServerUrl().empty() &&
!defaultAccount->getParams()->getConferenceFactoryUri().empty();
}
std::shared_ptr<linphone::ConferenceParams>
ToolModel::getChatRoomParams(std::shared_ptr<linphone::Call> call, std::shared_ptr<linphone::Address> remoteAddress) {
auto core = call ? call->getCore() : CoreModel::getInstance()->getCore();
auto localAddress = call ? call->getCallLog()->getLocalAddress() : nullptr;
if (!remoteAddress && call) remoteAddress = call->getRemoteAddress()->clone();
remoteAddress->clean();
auto account = findAccount(localAddress);
if (!account) account = core->getDefaultAccount();
if (!account) qWarning() << "failed to get account, return";
@ -481,7 +489,7 @@ ToolModel::getChatRoomParams(std::shared_ptr<linphone::Call> call, std::shared_p
params->enableChat(true);
params->enableGroup(false);
//: Dummy subject
params->setSubject(Utils::appStringToCoreString(QObject::tr("chat_dummy_subject")));
params->setSubject("Dummy subject");
params->setAccount(account);
auto chatParams = params->getChatParams();
@ -498,12 +506,12 @@ ToolModel::getChatRoomParams(std::shared_ptr<linphone::Call> call, std::shared_p
chatParams->setBackend(linphone::ChatRoom::Backend::FlexisipChat);
params->setSecurityLevel(linphone::Conference::SecurityLevel::EndToEnd);
} else if (!accountParams->getInstantMessagingEncryptionMandatory()) {
if (SettingsModel::getInstance()->getCreateEndToEndEncryptedMeetingsAndGroupCalls()) {
if (isEndToEndEncryptedChatAvailable()) {
qDebug() << "Account is in interop mode but LIME is available, requesting E2E encryption";
chatParams->setBackend(linphone::ChatRoom::Backend::FlexisipChat);
params->setSecurityLevel(linphone::Conference::SecurityLevel::EndToEnd);
} else {
qDebug() << "Account is in interop mode and LIME is available, disabling E2E encryption";
qDebug() << "Account is in interop mode and LIME is not available, disabling E2E encryption";
chatParams->setBackend(linphone::ChatRoom::Backend::Basic);
params->setSecurityLevel(linphone::Conference::SecurityLevel::None);
}
@ -554,12 +562,14 @@ std::shared_ptr<linphone::ChatRoom> ToolModel::lookupChatForAddress(std::shared_
auto core = CoreModel::getInstance()->getCore();
auto account = core->getDefaultAccount();
if (!account) return nullptr;
auto localAddress = account->getParams()->getIdentityAddress();
auto localAddress = account->getParams()->getIdentityAddress()->clone();
localAddress->clean();
if (!localAddress || !remoteAddress) return nullptr;
auto params = getChatRoomParams(nullptr, remoteAddress);
std::list<std::shared_ptr<linphone::Address>> participants;
participants.push_back(remoteAddress->clone());
remoteAddress->clean();
participants.push_back(remoteAddress);
qDebug() << "Looking for chat with local address" << localAddress->asStringUriOnly() << "and participant"
<< remoteAddress->asStringUriOnly();
@ -581,6 +591,33 @@ std::shared_ptr<linphone::ChatRoom> ToolModel::createChatForAddress(std::shared_
auto chatRoom = core->createChatRoom(params, participants);
return chatRoom;
}
std::shared_ptr<linphone::ChatRoom>
ToolModel::createGroupChatRoom(QString subject, std::list<std::shared_ptr<linphone::Address>> participantsAddresses) {
auto core = CoreModel::getInstance()->getCore();
auto account = core->getDefaultAccount();
auto params = core->createConferenceParams(nullptr);
params->enableChat(true);
params->enableGroup(true);
params->setSubject(Utils::appStringToCoreString(subject));
params->setAccount(account);
params->setSecurityLevel(linphone::Conference::SecurityLevel::EndToEnd);
auto chatParams = params->getChatParams();
if (!chatParams) {
qWarning() << "failed to get chat params from conference params, return";
return nullptr;
}
chatParams->setEphemeralLifetime(0);
chatParams->setBackend(linphone::ChatRoom::Backend::FlexisipChat);
auto accountParams = account->getParams();
auto chatRoom = core->createChatRoom(params, participantsAddresses);
return chatRoom;
}
// Presence mapping from SDK PresenceModel/RFC 3863 <-> Linphone UI (5 statuses Online, Offline, Away, Busy, DND).
// Online = Basic Status open with no activity
// Busy = Basic Status open with activity Busy and description busy
@ -589,11 +626,10 @@ std::shared_ptr<linphone::ChatRoom> ToolModel::createChatForAddress(std::shared_
// DND = Basic Status open with activity Other and description dnd
// Note : close status on the last 2 items would be preferrable, but they currently trigger multiple tuple NOTIFY from
// flexisip presence server Note 2 : close status with no activity triggers an unsubscribe.
LinphoneEnums::Presence
ToolModel::corePresenceModelToAppPresence(std::shared_ptr<const linphone::PresenceModel> presenceModel) {
if (!presenceModel) {
lWarning() << sLog().arg("presence model is null.");
// lWarning() << sLog().arg("presence model is null.");
return LinphoneEnums::Presence::Undefined;
}

View file

@ -85,8 +85,11 @@ public:
getChatRoomParams(std::shared_ptr<linphone::Call> call, std::shared_ptr<linphone::Address> remoteAddress = nullptr);
static std::shared_ptr<linphone::ChatRoom> lookupCurrentCallChat(std::shared_ptr<CallModel> callModel);
static std::shared_ptr<linphone::ChatRoom> createCurrentCallChat(std::shared_ptr<CallModel> callModel);
static bool isEndToEndEncryptedChatAvailable();
static std::shared_ptr<linphone::ChatRoom> lookupChatForAddress(std::shared_ptr<linphone::Address> remoteAddress);
static std::shared_ptr<linphone::ChatRoom> createChatForAddress(std::shared_ptr<linphone::Address> remoteAddress);
static std::shared_ptr<linphone::ChatRoom>
createGroupChatRoom(QString subject, std::list<std::shared_ptr<linphone::Address>> participantsAddresses);
static LinphoneEnums::Presence
corePresenceModelToAppPresence(std::shared_ptr<const linphone::PresenceModel> presenceModel);

View file

@ -98,6 +98,7 @@ public:
Q_PROPERTY(QString ContactUrl MEMBER ContactUrl CONSTANT)
Q_PROPERTY(QString TranslationUrl MEMBER TranslationUrl CONSTANT)
Q_PROPERTY(QString DefaultFont MEMBER DefaultFont CONSTANT)
Q_PROPERTY(QString DefaultLocale MEMBER DefaultLocale CONSTANT)
Q_PROPERTY(int maxMosaicParticipants MEMBER MaxMosaicParticipants CONSTANT)
Q_PROPERTY(QStringList reactionsList READ getReactionsList CONSTANT)

View file

@ -1556,6 +1556,7 @@ VariantObject *Utils::getChatForAddress(QString address) {
data->makeRequest([address, data]() {
auto linAddr = ToolModel::interpretUrl(address);
if (!linAddr) return QVariant();
linAddr->clean();
auto linphoneChatRoom = ToolModel::lookupChatForAddress(linAddr);
if (linphoneChatRoom) {
auto chatCore = ChatCore::create(linphoneChatRoom);
@ -1584,6 +1585,28 @@ VariantObject *Utils::getChatForAddress(QString address) {
return data;
}
VariantObject *Utils::createGroupChat(QString subject, QStringList participantAddresses) {
VariantObject *data = new VariantObject("lookupCurrentCallChat");
if (!data) return nullptr;
data->makeRequest([subject, participantAddresses, data]() {
std::list<std::shared_ptr<linphone::Address>> addresses;
for (auto &addr : participantAddresses) {
auto linAddr = ToolModel::interpretUrl(addr);
if (linAddr) addresses.push_back(linAddr);
else lWarning() << "Could not interpret address" << addr;
}
auto linphoneChatRoom = ToolModel::createGroupChatRoom(subject, addresses);
if (linphoneChatRoom) {
auto chatCore = ChatCore::create(linphoneChatRoom);
return QVariant::fromValue(new ChatGui(chatCore));
} else {
return QVariant();
}
});
data->requestValue();
return data;
}
void Utils::openChat(ChatGui *chat) {
auto mainWindow = getMainWindow();
smartShowWindow(mainWindow);

View file

@ -148,6 +148,7 @@ public:
Q_INVOKABLE static VariantObject *getCurrentCallChat(CallGui *call);
Q_INVOKABLE static VariantObject *getChatForAddress(QString address);
Q_INVOKABLE static VariantObject *createGroupChat(QString subject, QStringList participantAddresses);
Q_INVOKABLE static void openChat(ChatGui *chat);
Q_INVOKABLE static bool isEmptyMessage(QString message);
Q_INVOKABLE static QString encodeTextToQmlRichFormat(const QString &text,

View file

@ -22,6 +22,8 @@ list(APPEND _LINPHONEAPP_QML_FILES
view/Control/Button/Settings/SwitchSetting.qml
view/Control/Container/Carousel.qml
view/Control/Container/CreationFormLayout.qml
view/Control/Container/GroupCreationFormLayout.qml
view/Control/Container/DetailLayout.qml
view/Control/Container/FormItemLayout.qml
view/Control/Container/ScrollBar.qml
@ -107,6 +109,7 @@ list(APPEND _LINPHONEAPP_QML_FILES
view/Control/Tool/Prototype/PhoneNumberPrototype.qml
view/Page/Form/Call/NewCallForm.qml
view/Page/Form/Chat/NewChatForm.qml
view/Page/Form/Chat/SelectedChatView.qml
view/Page/Form/Contact/ContactDescription.qml
view/Page/Form/Contact/ContactEdition.qml

View file

@ -0,0 +1,109 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls.Basic as Control
import QtQuick.Effects
import Linphone
import UtilsCpp
import SettingsCpp
FocusScope {
id: mainItem
property color searchBarColor: DefaultStyle.grey_100
property color searchBarBorderColor: "transparent"
property alias searchBar: searchBar
property string startGroupButtonText
property NumericPadPopup numPadPopup
signal groupCreationRequested()
signal contactClicked(FriendGui contact)
clip: true
property alias topContent: topLayout.data
property bool topLayoutVisible: topLayout.children.length > 0
ColumnLayout {
anchors.fill: parent
spacing: Math.round(22 * DefaultStyle.dp)
ColumnLayout {
id: topLayout
visible: mainItem.topLayoutVisible
spacing: Math.round(18 * DefaultStyle.dp)
}
ColumnLayout {
onVisibleChanged: if (!visible && mainItem.numPadPopup) mainItem.numPadPopup.close()
spacing: Math.round(38 * DefaultStyle.dp)
SearchBar {
id: searchBar
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
Layout.rightMargin: Math.round(39 * DefaultStyle.dp)
focus: true
color: mainItem.searchBarColor
borderColor: mainItem.searchBarBorderColor
//: "Rechercher un contact"
placeholderText: qsTr("search_bar_look_for_contact_text")
numericPadPopup: mainItem.numPadPopup
KeyNavigation.down: grouCreationButton
}
ColumnLayout {
id: content
spacing: Math.round(32 * DefaultStyle.dp)
Button {
id: grouCreationButton
Layout.preferredWidth: Math.round(320 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(44 * DefaultStyle.dp)
padding: 0
KeyNavigation.up: searchBar
KeyNavigation.down: contactList
onClicked: mainItem.groupCreationRequested()
background: Rectangle {
anchors.fill: parent
radius: Math.round(50 * DefaultStyle.dp)
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: DefaultStyle.main2_100}
GradientStop { position: 1.0; color: DefaultStyle.grey_0}
}
}
contentItem: RowLayout {
spacing: Math.round(16 * DefaultStyle.dp)
anchors.verticalCenter: parent.verticalCenter
Image {
source: AppIcons.groupCall
Layout.preferredWidth: Math.round(44 * DefaultStyle.dp)
sourceSize.width: Math.round(44 * DefaultStyle.dp)
fillMode: Image.PreserveAspectFit
}
Text {
text: mainItem.startGroupButtonText
color: DefaultStyle.grey_1000
font {
pixelSize: Typography.h4.pixelSize
weight: Typography.h4.weight
}
}
Item {
Layout.fillWidth: true
}
EffectImage {
imageSource: AppIcons.rightArrow
Layout.preferredWidth: Math.round(24 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(24 * DefaultStyle.dp)
colorizationColor: DefaultStyle.main2_500main
}
}
}
AllContactListView{
id: contactList
Layout.fillWidth: true
Layout.fillHeight: true
showContactMenu: false
searchBarText: searchBar.text
onContactSelected: (contact) => {
mainItem.contactClicked(contact)
}
}
}
}
}
}

View file

@ -0,0 +1,101 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls.Basic as Control
import QtQuick.Effects
import Linphone
import UtilsCpp
import SettingsCpp
import "qrc:/qt/qml/Linphone/view/Style/buttonStyle.js" as ButtonStyle
FocusScope {
id: mainItem
property alias addParticipantsLayout: addParticipantsLayout
property alias groupName: groupName
property string formTitle
property string createGroupButtonText
property int selectedParticipantsCount
signal returnRequested()
signal groupCreationRequested()
ColumnLayout {
spacing: 0
anchors.fill: parent
RowLayout {
spacing: Math.round(10 * DefaultStyle.dp)
Button {
id: backGroupCallButton
style: ButtonStyle.noBackgroundOrange
icon.source: AppIcons.leftArrow
Layout.preferredWidth: Math.round(24 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(24 * DefaultStyle.dp)
KeyNavigation.down: groupName
KeyNavigation.right: groupCallButton
KeyNavigation.left: groupCallButton
onClicked: {
mainItem.returnRequested()
}
}
Text {
text: mainItem.formTitle
color: DefaultStyle.main1_500_main
maximumLineCount: 1
font {
pixelSize: Math.round(18 * DefaultStyle.dp)
weight: Typography.h4.weight
}
Layout.fillWidth: true
}
SmallButton {
id: groupCallButton
enabled: mainItem.selectedParticipantsCount.length != 0
Layout.rightMargin: Math.round(21 * DefaultStyle.dp)
text: mainItem.createGroupButtonText
style: ButtonStyle.main
KeyNavigation.down: addParticipantsLayout
KeyNavigation.left: backGroupCallButton
KeyNavigation.right: backGroupCallButton
onClicked: {
mainItem.groupCreationRequested()
}
}
}
RowLayout {
spacing: 0
Layout.topMargin: Math.round(18 * DefaultStyle.dp)
Layout.rightMargin: Math.round(38 * DefaultStyle.dp)
Text {
font.pixelSize: Typography.p2.pixelSize
font.weight: Typography.p2.weight
//: "Nom du groupe"
text: qsTr("history_group_call_start_dialog_subject_hint")
}
Item {
Layout.fillWidth: true
}
Text {
font.pixelSize: Math.round(12 * DefaultStyle.dp)
font.weight: Math.round(300 * DefaultStyle.dp)
//: "Requis"
text: qsTr("required")
}
}
TextField {
id: groupName
Layout.fillWidth: true
Layout.rightMargin: Math.round(38 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(49 * DefaultStyle.dp)
focus: true
KeyNavigation.down: addParticipantsLayout //participantList.count > 0 ? participantList : searchbar
}
AddParticipantsForm {
id: addParticipantsLayout
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: Math.round(15 * DefaultStyle.dp)
onSelectedParticipantsCountChanged: mainItem.selectedParticipantsCount = selectedParticipantsCount
focus: true
}
}
}

View file

@ -17,6 +17,9 @@ ListView {
property string searchText: searchBar?.text
property real busyIndicatorSize: Math.round(60 * DefaultStyle.dp)
property ChatGui currentChatGui
onCurrentIndexChanged: currentChatGui = model.getAt(currentIndex) || null
signal resultsReceived
onResultsReceived: {
@ -43,6 +46,10 @@ ListView {
mainItem.currentIndex = -1
mainItem.currentIndex = indexToSelect
}
onLayoutChanged: {
var chatToSelect = getAt(mainItem.currentIndex)
selectChat(mainItem.currentChatGui)
}
}
// flickDeceleration: 10000
spacing: Math.round(10 * DefaultStyle.dp)
@ -50,6 +57,14 @@ ListView {
function selectChat(chatGui) {
var index = chatProxy.findChatIndex(chatGui)
mainItem.currentIndex = index
// if the chat exists, it may not be displayed
// in list if hide_empty_chatrooms is set. Thus, we need
// to force adding it in the list so it is displayed
if (index === -1 && chatGui) {
chatProxy.addChatInList(chatGui)
var index = chatProxy.findChatIndex(chatGui)
mainItem.currentIndex = index
}
}
Component.onCompleted: cacheBuffer = Math.max(contentHeight, 0) //contentHeight>0 ? contentHeight : 0// cache all items
@ -67,9 +82,6 @@ ListView {
chatProxy.displayMore()
}
}
onCountChanged: {
if (count > 0 && currentIndex < 0) currentIndex = 0
}
//----------------------------------------------------------------
function moveToCurrentItem() {

View file

@ -4,7 +4,7 @@ import QtQuick.Controls.Basic as Control
import Linphone
import UtilsCpp 1.0
import ConstantsCpp 1.0
import ConstantsCpp
import SettingsCpp
import "qrc:/qt/qml/Linphone/view/Style/buttonStyle.js" as ButtonStyle

View file

@ -25,7 +25,7 @@ Notification {
property string message: notificationData ? notificationData.message : ""
Connections {
enabled: chat
target: chat.core
target: chat ? chat.core : null
function onMessageOpen() {
close()
}

View file

@ -7,26 +7,17 @@ import Linphone
import UtilsCpp
import SettingsCpp
FocusScope {
CreationFormLayout {
id: mainItem
property bool groupCallVisible
property bool displayCurrentCalls: false
property color searchBarColor: DefaultStyle.grey_100
property color searchBarBorderColor: "transparent"
property alias searchBar: searchBar
property NumericPadPopup numPadPopup
signal callButtonPressed(string address)
signal groupCallCreationRequested()
signal transferCallToAnotherRequested(CallGui dest)
signal contactClicked(FriendGui contact)
clip: true
ColumnLayout {
anchors.fill: parent
spacing: Math.round(22 * DefaultStyle.dp)
ColumnLayout {
spacing: Math.round(18 * DefaultStyle.dp)
visible: mainItem.displayCurrentCalls && callList.count > 0
//: Appel de groupe
startGroupButtonText: qsTr("call_start_group_call_title")
topLayoutVisible: mainItem.displayCurrentCalls && callList.count > 0
topContent: [
Text {
//: "Appels en cours"
text: qsTr("call_transfer_active_calls_label")
@ -34,7 +25,7 @@ FocusScope {
pixelSize: Typography.h4.pixelSize
weight: Typography.h4.weight
}
}
},
Flickable {
Layout.fillWidth: true
Layout.preferredHeight: callListBackground.height
@ -54,84 +45,5 @@ FocusScope {
}
}
}
}
ColumnLayout {
onVisibleChanged: if (!visible) mainItem.numPadPopup.close()
spacing: Math.round(38 * DefaultStyle.dp)
SearchBar {
id: searchBar
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
Layout.rightMargin: Math.round(39 * DefaultStyle.dp)
focus: true
color: mainItem.searchBarColor
borderColor: mainItem.searchBarBorderColor
//: "Rechercher un contact"
placeholderText: qsTr("search_bar_look_for_contact_text")
numericPadPopup: mainItem.numPadPopup
KeyNavigation.down: grouCallButton
}
ColumnLayout {
id: content
spacing: Math.round(32 * DefaultStyle.dp)
Button {
id: grouCallButton
visible: mainItem.groupCallVisible && !SettingsCpp.disableMeetingsFeature
Layout.preferredWidth: Math.round(320 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(44 * DefaultStyle.dp)
padding: 0
KeyNavigation.up: searchBar
KeyNavigation.down: contactList
onClicked: mainItem.groupCallCreationRequested()
background: Rectangle {
anchors.fill: parent
radius: Math.round(50 * DefaultStyle.dp)
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: DefaultStyle.main2_100}
GradientStop { position: 1.0; color: DefaultStyle.grey_0}
}
}
contentItem: RowLayout {
spacing: Math.round(16 * DefaultStyle.dp)
anchors.verticalCenter: parent.verticalCenter
Image {
source: AppIcons.groupCall
Layout.preferredWidth: Math.round(44 * DefaultStyle.dp)
sourceSize.width: Math.round(44 * DefaultStyle.dp)
fillMode: Image.PreserveAspectFit
}
Text {
text: qsTr("call_start_group_call_title")
color: DefaultStyle.grey_1000
font {
pixelSize: Typography.h4.pixelSize
weight: Typography.h4.weight
}
}
Item {
Layout.fillWidth: true
}
EffectImage {
imageSource: AppIcons.rightArrow
Layout.preferredWidth: Math.round(24 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(24 * DefaultStyle.dp)
colorizationColor: DefaultStyle.main2_500main
}
}
}
AllContactListView{
id: contactList
Layout.fillWidth: true
Layout.fillHeight: true
showContactMenu: false
searchBarText: searchBar.text
onContactSelected: (contact) => {
mainItem.contactClicked(contact)
}
}
}
}
}
]
}

View file

@ -0,0 +1,15 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls.Basic as Control
import QtQuick.Effects
import Linphone
import UtilsCpp
import SettingsCpp
CreationFormLayout {
id: mainItem
//: Nouveau groupe
startGroupButtonText: qsTr("chat_start_group_chat_title")
}

View file

@ -0,0 +1,15 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls.Basic as Control
import QtQuick.Effects
import Linphone
import UtilsCpp
import SettingsCpp
CreationFormLayout {
id: mainItem
//: Nouveau groupe
startGroupButtonText: qsTr("chat_start_group_chat_title")
}

View file

@ -25,52 +25,94 @@ FocusScope{
ColumnLayout {
anchors.fill: parent
spacing: Math.round(15 * DefaultStyle.dp)
ListView {
GridView {
id: participantList
Layout.fillWidth: true
visible: contentHeight > 0
Layout.preferredHeight: contentHeight
Layout.maximumHeight: mainItem.height / 3
width: mainItem.width
cellWidth: Math.round((50 + 18) * DefaultStyle.dp)
cellHeight: Math.round(80 * DefaultStyle.dp)
// columnCount: Math.floor(width/cellWidth)
model: mainItem.selectedParticipants
clip: true
// columnSpacing: Math.round(18 * DefaultStyle.dp)
// rowSpacing: Math.round(9 * DefaultStyle.dp)
Keys.onPressed: (event) => {
if(currentIndex <=0 && event.key == Qt.Key_Up){
nextItemInFocusChain(false).forceActiveFocus()
}
}
header: Text {
Layout.fillWidth: true
horizontalAlignment: Text.AlignLeft
visible: count > 0
//: "%n participant(s) sélectionné(s)"
text: qsTr("add_participant_selected_count", '', count).arg(count)
maximumLineCount: 1
color: DefaultStyle.grey_1000
font {
pixelSize: Math.round(12 * DefaultStyle.dp)
weight: Math.round(300 * DefaultStyle.dp)
}
}
delegate: FocusScope {
height: Math.round(56 * DefaultStyle.dp)
width: participantList.width - scrollbar.implicitWidth - Math.round(28 * DefaultStyle.dp)
RowLayout {
ColumnLayout {
anchors.fill: parent
spacing: Math.round(10 * DefaultStyle.dp)
spacing: Math.round(4 * DefaultStyle.dp)
width: Math.round(50 * DefaultStyle.dp)
Item {
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: Math.round(50 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(50 * DefaultStyle.dp)
Avatar {
Layout.preferredWidth: Math.round(45 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(45 * DefaultStyle.dp)
anchors.fill: parent
_address: modelData
shadowEnabled: false
}
Button {
Layout.preferredWidth: Math.round(17 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(17 * DefaultStyle.dp)
icon.width: Math.round(12 * DefaultStyle.dp)
icon.height: Math.round(12 * DefaultStyle.dp)
icon.source: AppIcons.closeX
anchors.top: parent.top
anchors.right: parent.right
background: Item {
Rectangle {
id: backgroundRect
color: DefaultStyle.grey_0
anchors.fill: parent
radius: Math.round(50 * DefaultStyle.dp)
}
MultiEffect {
anchors.fill: backgroundRect
source: backgroundRect
shadowEnabled: true
shadowColor: DefaultStyle.grey_1000
shadowBlur: 0.1
shadowOpacity: 0.5
}
}
onClicked: contactList.removeSelectedContactByAddress(modelData)
}
}
Text {
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: width
width: Math.round(50 * DefaultStyle.dp)
maximumLineCount: 1
clip: true
property var nameObj: UtilsCpp.getDisplayName(modelData)
text: nameObj ? nameObj.value : ""
font.pixelSize: Math.round(14 * DefaultStyle.dp)
font.capitalization: Font.Capitalize
color: DefaultStyle.main2_700
wrapMode: Text.WrapAnywhere
font {
pixelSize: Typography.p3.pixelSize
weight: Typography.p3.weight
capitalization: Font.Capitalize
}
Item {
Layout.fillWidth: true
}
Button {
Layout.preferredWidth: Math.round(24 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(24 * DefaultStyle.dp)
style: ButtonStyle.noBackgroundOrange
icon.source: AppIcons.closeX
icon.width: Math.round(24 * DefaultStyle.dp)
icon.height: Math.round(24 * DefaultStyle.dp)
focus: true
onClicked: contactList.removeSelectedContactByAddress(modelData)
}
}
}

View file

@ -35,7 +35,6 @@ AbstractMainPage {
property bool isRegistered: account ? account.core?.registrationState
== LinphoneEnums.RegistrationState.Ok : false
property int selectedParticipantsCount
signal startGroupCallRequested
signal createCallFromSearchBarRequested
signal createContactRequested(string name, string address)
signal openNumPadRequest
@ -370,7 +369,7 @@ AbstractMainPage {
onContactClicked: contact => {
mainWindow.startCallWithContact(contact, false, callContactsList)
}
onGroupCallCreationRequested: {
onGroupCreationRequested: {
console.log("groupe call requetsed")
listStackView.push(groupCallItem)
}
@ -387,110 +386,21 @@ AbstractMainPage {
Component {
id: groupCallItem
FocusScope {
GroupCreationFormLayout {
objectName: "groupCallItem"
//: "Appel de groupe"
formTitle: qsTr("call_start_group_call_title")
//: "Lancer"
createGroupButtonText: qsTr("call_action_start_group_call")
Control.StackView.onActivated: {
addParticipantsLayout.forceActiveFocus()
}
ColumnLayout {
spacing: 0
anchors.fill: parent
RowLayout {
spacing: Math.round(10 * DefaultStyle.dp)
visible: !SettingsCpp.disableMeetingsFeature
Button {
id: backGroupCallButton
style: ButtonStyle.noBackgroundOrange
icon.source: AppIcons.leftArrow
Layout.preferredWidth: Math.round(24 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(24 * DefaultStyle.dp)
KeyNavigation.down: listStackView
KeyNavigation.right: groupCallButton
KeyNavigation.left: groupCallButton
onClicked: {
onReturnRequested: {
listStackView.pop()
listStackView.currentItem?.forceActiveFocus()
}
}
ColumnLayout {
spacing: Math.round(3 * DefaultStyle.dp)
Text {
//: "Appel de groupe"
text: qsTr("call_start_group_call_title")
color: DefaultStyle.main1_500_main
maximumLineCount: 1
font {
pixelSize: Math.round(18 * DefaultStyle.dp)
weight: Typography.h4.weight
}
Layout.fillWidth: true
}
Text {
//: "%n participant(s) sélectionné(s)"
text: qsTr("group_call_participant_selected", '', mainItem.selectedParticipantsCount).arg(mainItem.selectedParticipantsCount)
color: DefaultStyle.main2_500main
maximumLineCount: 1
font {
pixelSize: Math.round(12 * DefaultStyle.dp)
weight: Math.round(300 * DefaultStyle.dp)
}
Layout.fillWidth: true
}
}
SmallButton {
id: groupCallButton
enabled: mainItem.selectedParticipantsCount.length != 0
Layout.rightMargin: Math.round(21 * DefaultStyle.dp)
//: "Lancer"
text: qsTr("call_action_start_group_call")
style: ButtonStyle.main
KeyNavigation.down: listStackView
KeyNavigation.left: backGroupCallButton
KeyNavigation.right: backGroupCallButton
onClicked: {
mainItem.startGroupCallRequested()
}
}
}
RowLayout {
spacing: 0
Layout.topMargin: Math.round(18 * DefaultStyle.dp)
Layout.rightMargin: Math.round(38 * DefaultStyle.dp)
Text {
font.pixelSize: Typography.p2.pixelSize
font.weight: Typography.p2.weight
//: "Nom du groupe"
text: qsTr("history_group_call_start_dialog_subject_hint")
}
Item {
Layout.fillWidth: true
}
Text {
font.pixelSize: Math.round(12 * DefaultStyle.dp)
font.weight: Math.round(300 * DefaultStyle.dp)
//: "Requis"
text: qsTr("required")
}
}
TextField {
id: groupCallName
Layout.fillWidth: true
Layout.rightMargin: Math.round(38 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(49 * DefaultStyle.dp)
focus: true
KeyNavigation.down: addParticipantsLayout //participantList.count > 0 ? participantList : searchbar
}
AddParticipantsForm {
id: addParticipantsLayout
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: Math.round(15 * DefaultStyle.dp)
onSelectedParticipantsCountChanged: mainItem.selectedParticipantsCount = selectedParticipantsCount
focus: true
Connections {
target: mainItem
function onStartGroupCallRequested() {
if (groupCallName.text.length === 0) {
onGroupCreationRequested: {
if (groupName.text.length === 0) {
UtilsCpp.showInformationPopup(qsTr("information_popup_error_title"),
//: "Un nom doit être donné à l'appel de groupe
qsTr("group_call_error_must_have_name"), false)
@ -499,17 +409,7 @@ AbstractMainPage {
//: "Vous n'etes pas connecté"
qsTr("group_call_error_not_connected"), false)
} else {
// mainItem.confInfoGui = Qt.createQmlObject(
// "import Linphone
// ConferenceInfoGui{}", mainItem)
// mainItem.confInfoGui.core.subject = groupCallName.text
// mainItem.confInfoGui.core.isScheduled = false
// mainItem.confInfoGui.core.addParticipants(addParticipantsLayout.selectedParticipants)
// mainItem.confInfoGui.core.save()
UtilsCpp.createGroupCall(groupCallName.text, addParticipantsLayout.selectedParticipants)
}
}
}
UtilsCpp.createGroupCall(groupName.text, addParticipantsLayout.selectedParticipants)
}
}
}

View file

@ -16,24 +16,41 @@ AbstractMainPage {
emptyListText: qsTr("chat_empty_title")
newItemIconSource: AppIcons.plusCircle
property var selectedChatGui
property AccountProxy accounts: AccountProxy {
id: accountProxy
sourceModel: AppCpp.accounts
}
property AccountGui account: accountProxy.defaultAccount
property var state: account && account.core?.registrationState || 0
property bool isRegistered: account ? account.core?.registrationState
== LinphoneEnums.RegistrationState.Ok : false
property var selectedChatGui
property string remoteAddress
onRemoteAddressChanged: console.log("ChatPage : remote address changed :", remoteAddress)
property var remoteChatObj: UtilsCpp.getChatForAddress(remoteAddress)
property ChatGui remoteChat: remoteChatObj && remoteChatObj.value ? remoteChatObj.value : null
onRemoteChatChanged: if (remoteChat) selectedChatGui = remoteChat
property var remoteChat: remoteChatObj ? remoteChatObj.value : null
onRemoteChatChanged: {
selectedChatGui = remoteChat
}
onSelectedChatGuiChanged: {
if (selectedChatGui)
if (selectedChatGui) {
if (!listStackView.currentItem || listStackView.currentItem.objectName !== "chatListItem") {
listStackView.popToIndex(0)
if (listStackView.depth === 0 || listStackView.currentItem.objectName !== "chatListItem") listStackView.push(chatListItem)
}
rightPanelStackView.replace(currentChatComp,
Control.StackView.Immediate)
else
}
else {
rightPanelStackView.replace(emptySelection,
Control.StackView.Immediate)
}
}
rightPanelStackView.initialItem: emptySelection
rightPanelStackView.visible: listStackView.currentItem && listStackView.currentItem.objectName === "chatListItem"
onNoItemButtonPressed: goToNewChat()
@ -206,36 +223,79 @@ AbstractMainPage {
//: "New chat"
text: qsTr("chat_action_start_new_chat")
color: DefaultStyle.main2_700
font.pixelSize: Typography.h2.pixelSize
font.weight: Typography.h2.weight
font.pixelSize: Typography.h2m.pixelSize
font.weight: Typography.h2m.weight
}
Item {
Layout.fillWidth: true
}
}
// NewCallForm {
// id: callContactsList
// Layout.topMargin: Math.round(18 * DefaultStyle.dp)
// Layout.fillWidth: true
// Layout.fillHeight: true
// focus: true
// numPadPopup: numericPadPopupItem
// groupCallVisible: true
// searchBarColor: DefaultStyle.grey_100
// onContactClicked: contact => {
// mainWindow.startCallWithContact(contact, false, callContactsList)
// }
// onGroupCallCreationRequested: {
// console.log("groupe call requetsed")
// listStackView.push(groupCallItem)
// }
// Connections {
// target: mainItem
// function onCreateCallFromSearchBarRequested() {
// UtilsCpp.createCall(callContactsList.searchBar.text)
// }
// }
// }
NewChatForm {
id: newChatForm
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: Math.round(18 * DefaultStyle.dp)
onGroupCreationRequested: {
console.log("groupe call requetsed")
listStackView.push(groupChatItem)
}
onContactClicked: (contact) => {
if (contact) {
mainItem.remoteAddress = ""
mainItem.remoteAddress = contact.core.defaultAddress
}
}
}
}
}
}
Component {
id: groupChatItem
GroupCreationFormLayout {
id: chatCreationLayout
objectName: "groupChatItem"
//: "Nouveau groupe"
formTitle: qsTr("chat_start_group_chat_title")
//: "Créer"
createGroupButtonText: qsTr("chat_action_start_group_chat")
property var groupChatObj
property var groupChat: groupChatObj ? groupChatObj.value : null
onGroupChatChanged: if (groupChat && groupChat.core.state === LinphoneEnums.ChatRoomState.Created) {
mainItem.selectedChatGui = groupChat
}
Connections {
enabled: groupChat || false
target: groupChat?.core || null
function onChatRoomStateChanged() {
if (chatCreationLayout.groupChat.core.state === LinphoneEnums.ChatRoomState.Created) {
mainItem.selectedChatGui = chatCreationLayout.groupChat
}
}
}
Control.StackView.onActivated: {
addParticipantsLayout.forceActiveFocus()
}
onReturnRequested: {
listStackView.pop()
listStackView.currentItem?.forceActiveFocus()
}
onGroupCreationRequested: {
if (groupName.text.length === 0) {
UtilsCpp.showInformationPopup(qsTr("information_popup_error_title"),
//: "Un nom doit être donné au groupe
qsTr("group_chat_error_must_have_name"), false)
} else if (!mainItem.isRegistered) {
UtilsCpp.showInformationPopup(qsTr("information_popup_error_title"),
//: "Vous n'etes pas connecté"
qsTr("group_call_error_not_connected"), false)
} else {
console.log("create group chat")
chatCreationLayout.groupChatObj = UtilsCpp.createGroupChat(chatCreationLayout.groupName.text, addParticipantsLayout.selectedParticipants)
}
}
}
}

View file

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