copy message text to clipboard + improve chat message and sending area ui

This commit is contained in:
Gaelle Braud 2025-05-07 11:02:01 +02:00
parent ecd9373df9
commit 73b83771be
9 changed files with 197 additions and 113 deletions

View file

@ -97,10 +97,12 @@ void ChatList::setSelf(QSharedPointer<ChatList> me) {
std::find_if(list.begin(), list.end(), [chatRoom](const QSharedPointer<ChatCore> &item) { std::find_if(list.begin(), list.end(), [chatRoom](const QSharedPointer<ChatCore> &item) {
return (item && item->getModel()->getMonitor() == chatRoom); return (item && item->getModel()->getMonitor() == chatRoom);
}); });
if (found != list.end()) { if (found == list.end()) {
qDebug() << "chat room created, add it to the list";
auto model = createChatCore(chatRoom); auto model = createChatCore(chatRoom);
mModelConnection->invokeToCore([this, model]() { add(model); }); mModelConnection->invokeToCore([this, model]() {
add(model);
emit chatAdded();
});
} }
} }
} }

View file

@ -47,6 +47,7 @@ signals:
void lUpdate(); void lUpdate();
void filterChanged(QString filter); void filterChanged(QString filter);
void chatRemoved(ChatGui *chat); void chatRemoved(ChatGui *chat);
void chatAdded();
private: private:
QString mFilter; QString mFilter;

View file

@ -28,6 +28,7 @@ DEFINE_ABSTRACT_OBJECT(ChatProxy)
ChatProxy::ChatProxy(QObject *parent) : LimitProxy(parent) { ChatProxy::ChatProxy(QObject *parent) : LimitProxy(parent) {
mList = ChatList::create(); mList = ChatList::create();
setSourceModel(mList.get()); setSourceModel(mList.get());
setDynamicSortFilter(true);
} }
ChatProxy::~ChatProxy() { ChatProxy::~ChatProxy() {
@ -43,8 +44,10 @@ void ChatProxy::setSourceModel(QAbstractItemModel *model) {
connect(this, &ChatProxy::filterTextChanged, newChatList, connect(this, &ChatProxy::filterTextChanged, newChatList,
[this, newChatList] { emit newChatList->filterChanged(getFilterText()); }); [this, newChatList] { emit newChatList->filterChanged(getFilterText()); });
connect(newChatList, &ChatList::chatRemoved, this, &ChatProxy::chatRemoved); connect(newChatList, &ChatList::chatRemoved, this, &ChatProxy::chatRemoved);
connect(newChatList, &ChatList::chatAdded, this, [this] { invalidate(); });
} }
setSourceModels(new SortFilterList(model)); auto firstList = new SortFilterList(model, Qt::AscendingOrder);
setSourceModels(firstList);
sort(0); sort(0);
} }

View file

@ -13,9 +13,11 @@ Button {
radius: Math.round(5 * DefaultStyle.dp) radius: Math.round(5 * DefaultStyle.dp)
shadowEnabled: mainItem.activeFocus || hovered shadowEnabled: mainItem.activeFocus || hovered
style: ButtonStyle.hoveredBackground style: ButtonStyle.hoveredBackground
property bool inverseLayout: false
contentItem: RowLayout { contentItem: RowLayout {
spacing: Math.round(5 * DefaultStyle.dp) spacing: Math.round(5 * DefaultStyle.dp)
layoutDirection: mainItem.inverseLayout ? Qt.RightToLeft: Qt.LeftToRight
EffectImage { EffectImage {
imageSource: mainItem.icon.source imageSource: mainItem.icon.source
imageWidth: mainItem.icon.width imageWidth: mainItem.icon.width
@ -33,6 +35,7 @@ Button {
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
Layout.preferredWidth: textMetrics.advanceWidth Layout.preferredWidth: textMetrics.advanceWidth
Layout.fillWidth: true
wrapMode: Text.WrapAnywhere wrapMode: Text.WrapAnywhere
text: mainItem.text text: mainItem.text
maximumLineCount: 1 maximumLineCount: 1

View file

@ -8,137 +8,177 @@ import SettingsCpp
import "qrc:/qt/qml/Linphone/view/Style/buttonStyle.js" as ButtonStyle import "qrc:/qt/qml/Linphone/view/Style/buttonStyle.js" as ButtonStyle
import "qrc:/qt/qml/Linphone/view/Control/Tool/Helper/utils.js" as Utils import "qrc:/qt/qml/Linphone/view/Control/Tool/Helper/utils.js" as Utils
Control.Control {
RowLayout {
id: mainItem id: mainItem
property color backgroundColor property color backgroundColor
property bool isFirstMessage property bool isFirstMessage
property string imgUrl property string imgUrl
spacing: 0
property ChatMessageGui chatMessage property ChatMessageGui chatMessage
property string fromAddress: chatMessage? chatMessage.core.fromAddress : "" property string fromAddress: chatMessage? chatMessage.core.fromAddress : ""
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
hoverEnabled: true
signal messageDeletionRequested() signal messageDeletionRequested()
Avatar { background: Item {
id: avatar anchors.fill: parent
visible: mainItem.isFromChatGroup Text {
opacity: mainItem.isRemoteMessage && mainItem.isFirstMessage ? 1 : 0 id: fromNameText
Layout.preferredWidth: 26 * DefaultStyle.dp visible: mainItem.isFromChatGroup && mainItem.isRemoteMessage && mainItem.isFirstMessage
Layout.preferredHeight: 26 * DefaultStyle.dp anchors.top: parent.top
Layout.alignment: Qt.AlignTop maximumLineCount: 1
Layout.topMargin: isFirstMessage ? 16 * DefaultStyle.dp : 0 width: implicitWidth
_address: chatMessage ? chatMessage.core.fromAddress : "" x: chatBubble.x
} text: mainItem.chatMessage.core.fromName
Control.Control { color: DefaultStyle.main2_500main
Layout.topMargin: isFirstMessage ? 16 * DefaultStyle.dp : 0 font {
Layout.leftMargin: mainItem.isFromChatGroup ? Math.round(9 * DefaultStyle.dp) : 0 pixelSize: Typography.p4.pixelSize
Layout.preferredWidth: Math.min(implicitWidth, mainItem.maxWidth - avatar.implicitWidth) weight: Typography.p4.weight
// Layout.topMargin: name.visible ? Math.round(7 * DefaultStyle.dp) : 0
topPadding: Math.round(12 * DefaultStyle.dp)
bottomPadding: Math.round(12 * DefaultStyle.dp)
leftPadding: Math.round(18 * DefaultStyle.dp)
rightPadding: Math.round(18 * DefaultStyle.dp)
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: (mouse) => {
console.log("message clicked")
if (mouse.button === Qt.RightButton) {
optionsMenu.x = mouse.x
optionsMenu.open()
}
} }
} }
Popup { }
id: optionsMenu
contentItem: RowLayout {
spacing: 0
layoutDirection: mainItem.isRemoteMessage ? Qt.LeftToRight : Qt.RightToLeft
Avatar {
id: avatar
visible: mainItem.isFromChatGroup
opacity: mainItem.isRemoteMessage && mainItem.isFirstMessage ? 1 : 0
Layout.preferredWidth: 26 * DefaultStyle.dp
Layout.preferredHeight: 26 * DefaultStyle.dp
Layout.alignment: Qt.AlignTop
Layout.topMargin: isFirstMessage ? 16 * DefaultStyle.dp : 0
_address: chatMessage ? chatMessage.core.fromAddress : ""
}
Control.Control {
id: chatBubble
Layout.topMargin: isFirstMessage ? 16 * DefaultStyle.dp : 0
Layout.leftMargin: mainItem.isFromChatGroup ? Math.round(9 * DefaultStyle.dp) : 0
Layout.preferredWidth: Math.min(implicitWidth, mainItem.maxWidth - avatar.implicitWidth)
spacing: Math.round(2 * DefaultStyle.dp)
topPadding: Math.round(12 * DefaultStyle.dp)
bottomPadding: Math.round(12 * DefaultStyle.dp)
leftPadding: Math.round(18 * DefaultStyle.dp)
rightPadding: Math.round(18 * DefaultStyle.dp)
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: (mouse) => {
if (mouse.button === Qt.RightButton) {
optionsMenu.open()
}
}
}
background: Item { background: Item {
anchors.fill: parent anchors.fill: parent
Rectangle { Rectangle {
id: popupBackground
anchors.fill: parent anchors.fill: parent
color: DefaultStyle.grey_0 color: mainItem.backgroundColor
radius: Math.round(16 * DefaultStyle.dp) radius: Math.round(16 * DefaultStyle.dp)
} }
MultiEffect { Rectangle {
source: popupBackground visible: mainItem.isFirstMessage && mainItem.isRemoteMessage
anchors.fill: popupBackground anchors.top: parent.top
shadowEnabled: true anchors.left: parent.left
shadowBlur: 0.1 width: Math.round(parent.width / 4)
shadowColor: DefaultStyle.grey_1000 height: Math.round(parent.height / 4)
shadowOpacity: 0.4 color: mainItem.backgroundColor
}
Rectangle {
visible: mainItem.isFirstMessage && !mainItem.isRemoteMessage
anchors.bottom: parent.bottom
anchors.right: parent.right
width: Math.round(parent.width / 4)
height: Math.round(parent.height / 4)
color: mainItem.backgroundColor
} }
} }
contentItem: ColumnLayout { contentItem: ColumnLayout {
IconLabelButton { id: contentLayout
//: "Supprimer" Image {
text: qsTr("chat_message_delete") visible: mainItem.imgUrl != undefined
icon.source: AppIcons.trashCan id: contentimage
spacing: Math.round(10 * DefaultStyle.dp) }
Text {
visible: modelData.core.text != undefined
text: modelData.core.text
Layout.fillWidth: true Layout.fillWidth: true
onClicked: { color: DefaultStyle.main2_700
mainItem.messageDeletionRequested() font {
optionsMenu.close() pixelSize: Typography.p1.pixelSize
weight: Typography.p1.weight
}
}
Text {
Layout.alignment: Qt.AlignRight
text: UtilsCpp.formatDate(modelData.core.timestamp, true, false)
color: DefaultStyle.main2_500main
font {
pixelSize: Typography.p3.pixelSize
weight: Typography.p3.weight
} }
style: ButtonStyle.noBackgroundRed
} }
} }
} }
RowLayout {
background: Item { id: actionsLayout
anchors.fill: parent visible: mainItem.hovered || optionsMenu.hovered || optionsMenu.popup.opened || emojiButton.hovered
Rectangle { Layout.leftMargin: Math.round(8 * DefaultStyle.dp)
anchors.fill: parent Layout.rightMargin: Math.round(8 * DefaultStyle.dp)
color: mainItem.backgroundColor Layout.alignment: Qt.AlignVCenter
radius: Math.round(16 * DefaultStyle.dp) // Layout.fillWidth: true
} spacing: Math.round(7 * DefaultStyle.dp)
Rectangle { layoutDirection: mainItem.isRemoteMessage ? Qt.LeftToRight : Qt.RightToLeft
visible: mainItem.isFirstMessage && mainItem.isRemoteMessage PopupButton {
anchors.top: parent.top id: optionsMenu
anchors.left: parent.left popup.padding: 0
width: Math.round(parent.width / 4) popup.contentItem: ColumnLayout {
height: Math.round(parent.height / 4) spacing: 0
color: mainItem.backgroundColor IconLabelButton {
} inverseLayout: true
Rectangle { //: "Copy"
visible: mainItem.isFirstMessage && !mainItem.isRemoteMessage text: qsTr("chat_message_copy")
anchors.bottom: parent.bottom icon.source: AppIcons.copy
anchors.right: parent.right // spacing: Math.round(10 * DefaultStyle.dp)
width: Math.round(parent.width / 4) Layout.fillWidth: true
height: Math.round(parent.height / 4) Layout.preferredHeight: 45 * DefaultStyle.dp
color: mainItem.backgroundColor onClicked: {
} var success = UtilsCpp.copyToClipboard(modelData.core.text)
} //: Copied
contentItem: ColumnLayout { if (success) UtilsCpp.showInformationPopup(qsTr("chat_message_copied_to_clipboard_title"),
id: contentLayout //: "in clipboard"
Image { qsTr("chat_message_copied_to_clipboard_toast"))
visible: mainItem.imgUrl != undefined }
id: contentimage }
} IconLabelButton {
Text { inverseLayout: true
visible: modelData.core.text != undefined //: "Delete"
text: modelData.core.text text: qsTr("chat_message_delete")
Layout.fillWidth: true icon.source: AppIcons.trashCan
color: DefaultStyle.main2_700 // spacing: Math.round(10 * DefaultStyle.dp)
font { Layout.fillWidth: true
pixelSize: Typography.p1.pixelSize Layout.preferredHeight: 45 * DefaultStyle.dp
weight: Typography.p1.weight onClicked: {
mainItem.messageDeletionRequested()
optionsMenu.close()
}
style: ButtonStyle.hoveredBackgroundRed
}
} }
} }
Text { BigButton {
Layout.alignment: Qt.AlignRight id: emojiButton
text: UtilsCpp.formatDate(modelData.core.timestamp, true, false) style: ButtonStyle.noBackground
color: DefaultStyle.main2_500main icon.source: AppIcons.smiley
font {
pixelSize: Typography.p3.pixelSize
weight: Typography.p3.weight
}
} }
} }
Item{Layout.fillWidth: true}
} }
} }

View file

@ -14,13 +14,41 @@ ListView {
property color backgroundColor property color backgroundColor
spacing: Math.round(4 * DefaultStyle.dp) spacing: Math.round(4 * DefaultStyle.dp)
Component.onCompleted: positionViewAtEnd() // Component.onCompleted: positionViewAtIndex(chatMessageProxy.findFirstUnreadIndex(), ListView.Visible)
onCountChanged: positionViewAtEnd() onAtYEndChanged: if (atYEnd) chat.core.lMarkAsRead();
onChatChanged: if (visible) {
var index = chatMessageProxy.findFirstUnreadIndex()
console.log("visible, first unread at index", index)
mainItem.positionViewAtIndex(index, ListView.Visible)
}
RoundButton {
icon.source: AppIcons.downArrow
// Layout.preferredWidth: 40 * DefaultStyle.dp
// Layout.preferredHeight: 40 * DefaultStyle.dp
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.bottomMargin: Math.round(18 * DefaultStyle.dp)
anchors.rightMargin: Math.round(18 * DefaultStyle.dp)
onClicked: {
var index = chatMessageProxy.findFirstUnreadIndex()
console.log("clicked, first unread at index", index)
mainItem.positionViewAtIndex(index, ListView.Visible)
// var chatMessage = chatMessageProxy.getChatMessageAtIndex(index)
// if (chatMessage && !chatMessage.core.isRead) chatMessage.core.lMarkAsRead()
}
}
model: ChatMessageProxy { model: ChatMessageProxy {
id: chatMessageProxy id: chatMessageProxy
chatGui: mainItem.chat chatGui: mainItem.chat
onCountChanged: {
var indexToSelect = mainItem.currentIndex
mainItem.currentIndex = -1
mainItem.currentIndex = indexToSelect
}
} }
header: Item { header: Item {
@ -31,7 +59,7 @@ ListView {
chatMessage: modelData chatMessage: modelData
property real maxWidth: Math.round(mainItem.width * (3/4)) property real maxWidth: Math.round(mainItem.width * (3/4))
// height: childrenRect.height // height: childrenRect.height
// width: childrenRect.width width: mainItem.width
property var previousIndex: index - 1 property var previousIndex: index - 1
property var previousFromAddress: chatMessageProxy.getChatMessageAtIndex(index-1)?.core.fromAddress property var previousFromAddress: chatMessageProxy.getChatMessageAtIndex(index-1)?.core.fromAddress
backgroundColor: isRemoteMessage ? DefaultStyle.main2_100 : DefaultStyle.main1_100 backgroundColor: isRemoteMessage ? DefaultStyle.main2_100 : DefaultStyle.main1_100
@ -52,7 +80,7 @@ ListView {
bottomPadding: Math.round(5 * DefaultStyle.dp) bottomPadding: Math.round(5 * DefaultStyle.dp)
background: Rectangle { background: Rectangle {
anchors.fill: parent anchors.fill: parent
color: mainItem.panelColor color: mainItem.backgroundColor
} }
contentItem: RowLayout { contentItem: RowLayout {
id: composeLayout id: composeLayout

View file

@ -80,7 +80,7 @@ RowLayout {
ChatMessagesListView { ChatMessagesListView {
id: chatMessagesListView id: chatMessagesListView
height: contentHeight height: contentHeight
backgroundColor: panelColor backgroundColor: splitPanel.panelColor
width: parent.width - anchors.leftMargin - anchors.rightMargin width: parent.width - anchors.leftMargin - anchors.rightMargin
chat: mainItem.chat chat: mainItem.chat
anchors.top: parent.top anchors.top: parent.top
@ -146,8 +146,8 @@ RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
leftPadding: Math.round(15 * DefaultStyle.dp) leftPadding: Math.round(15 * DefaultStyle.dp)
rightPadding: Math.round(15 * DefaultStyle.dp) rightPadding: Math.round(15 * DefaultStyle.dp)
topPadding: Math.round(16 * DefaultStyle.dp) topPadding: Math.round(15 * DefaultStyle.dp)
bottomPadding: Math.round(16 * DefaultStyle.dp) bottomPadding: Math.round(15 * DefaultStyle.dp)
background: Rectangle { background: Rectangle {
id: inputBackground id: inputBackground
anchors.fill: parent anchors.fill: parent
@ -188,9 +188,15 @@ RowLayout {
height: sendingAreaFlickable.height height: sendingAreaFlickable.height
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
wrapMode: TextEdit.WordWrap
//: Say something : placeholder text for sending message text area //: Say something : placeholder text for sending message text area
placeholderText: qsTr("Dites quelque chose…") placeholderText: qsTr("Dites quelque chose…")
placeholderTextColor: DefaultStyle.main2_400 placeholderTextColor: DefaultStyle.main2_400
color: DefaultStyle.main2_700
font {
pixelSize: Typography.p1.pixelSize
weight: Typography.p1.weight
}
onCursorRectangleChanged: sendingAreaFlickable.ensureVisible(cursorRectangle) onCursorRectangleChanged: sendingAreaFlickable.ensureVisible(cursorRectangle)
property string previousText property string previousText
Component.onCompleted: previousText = text Component.onCompleted: previousText = text

View file

@ -684,8 +684,8 @@ AbstractMainPage {
KeyNavigation.up: deletePopup KeyNavigation.up: deletePopup
KeyNavigation.down: joinButton KeyNavigation.down: joinButton
onClicked: { onClicked: {
UtilsCpp.copyToClipboard(mainItem.selectedConference.core.uri) var success = UtilsCpp.copyToClipboard(mainItem.selectedConference.core.uri)
UtilsCpp.showInformationPopup(qsTr("saved"), if (success) UtilsCpp.showInformationPopup(qsTr("saved"),
//: "Adresse de la réunion copiée" //: "Adresse de la réunion copiée"
qsTr("meeting_address_copied_to_clipboard_toast")) qsTr("meeting_address_copied_to_clipboard_toast"))
} }

View file

@ -113,7 +113,8 @@
color: { color: {
normal: Linphone.DefaultStyle.grey_500, normal: Linphone.DefaultStyle.grey_500,
hovered: Linphone.DefaultStyle.grey_600, hovered: Linphone.DefaultStyle.grey_600,
pressed: Linphone.DefaultStyle.main2_400 pressed: Linphone.DefaultStyle.main2_400,
hovered: Linphone.DefaultStyle.main2_400
}, },
text: { text: {
normal: Linphone.DefaultStyle.grey_0, normal: Linphone.DefaultStyle.grey_0,