reverse list to load most recent messages first

This commit is contained in:
Gaelle Braud 2025-08-01 12:43:08 +02:00
parent d17a61e6d0
commit 3f3f29b2ec
5 changed files with 155 additions and 148 deletions

View file

@ -44,6 +44,7 @@ void EventLogProxy::setSourceModel(QAbstractItemModel *model) {
connect(newEventLogList, &EventLogList::eventChanged, this, &EventLogProxy::eventChanged);
connect(newEventLogList, &EventLogList::eventInserted, this,
[this, newEventLogList](int index, EventLogGui *event) {
invalidate();
if (index != -1) {
index = dynamic_cast<SortFilterList *>(sourceModel())
->mapFromSource(newEventLogList->index(index, 0))
@ -53,7 +54,7 @@ void EventLogProxy::setSourceModel(QAbstractItemModel *model) {
emit eventInserted(index, event);
});
}
setSourceModels(new SortFilterList(model, Qt::AscendingOrder));
setSourceModels(new SortFilterList(model, Qt::DescendingOrder));
sort(0);
}
@ -88,10 +89,10 @@ int EventLogProxy::findFirstUnreadIndex() {
if (mMaxDisplayItems <= listIndex) setMaxDisplayItems(listIndex + mDisplayItemsStep);
return listIndex;
} else {
return std::max(0, getCount() - 1);
return 0;
}
}
return std::max(0, getCount() - 1);
return 0;
}
void EventLogProxy::markIndexAsRead(int proxyIndex) {

View file

@ -2072,45 +2072,45 @@ Error</extracomment>
<context>
<name>ChatMessagesListView</name>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="35"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="49"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="36"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="50"/>
<source>popup_info_find_message_title</source>
<extracomment>Find message</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="37"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="38"/>
<source>info_popup_no_result_message</source>
<extracomment>No result found</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="51"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="52"/>
<source>info_popup_first_result_message</source>
<extracomment>First result reached</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="53"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="54"/>
<source>info_popup_last_result_message</source>
<extracomment>Last result reached</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="140"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="143"/>
<source>chat_message_list_encrypted_header_title</source>
<extracomment>End to end encrypted chat</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="150"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="153"/>
<source>chat_message_list_encrypted_header_message</source>
<extracomment>Les messages de cette conversation sont chiffrés de bout
en bout. Seul votre correspondant peut les déchiffrer.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="296"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="191"/>
<source>chat_message_is_writing_info</source>
<extracomment>%1 is writing</extracomment>
<translation type="unfinished"></translation>

View file

@ -2034,38 +2034,38 @@ Error</extracomment>
<context>
<name>ChatMessagesListView</name>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="35"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="49"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="36"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="50"/>
<source>popup_info_find_message_title</source>
<extracomment>Find message</extracomment>
<translation>Find message</translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="37"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="38"/>
<source>info_popup_no_result_message</source>
<extracomment>No result found</extracomment>
<translation>No result found</translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="51"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="52"/>
<source>info_popup_first_result_message</source>
<extracomment>First result reached</extracomment>
<translation>First result reached</translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="53"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="54"/>
<source>info_popup_last_result_message</source>
<extracomment>Last result reached</extracomment>
<translation>Last result reached</translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="140"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="143"/>
<source>chat_message_list_encrypted_header_title</source>
<extracomment>End to end encrypted chat</extracomment>
<translation>End to end encrypted chat</translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="150"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="153"/>
<source>chat_message_list_encrypted_header_message</source>
<extracomment>Les messages de cette conversation sont chiffrés de bout
en bout. Seul votre correspondant peut les déchiffrer.</extracomment>
@ -2073,7 +2073,7 @@ Error</extracomment>
Only your correspondent can decrypt them.</translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="296"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="191"/>
<source>chat_message_is_writing_info</source>
<extracomment>%1 is writing</extracomment>
<translation>%1 is writing</translation>

View file

@ -2034,38 +2034,38 @@ Error</extracomment>
<context>
<name>ChatMessagesListView</name>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="35"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="49"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="36"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="50"/>
<source>popup_info_find_message_title</source>
<extracomment>Find message</extracomment>
<translation>Trouver un message</translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="37"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="38"/>
<source>info_popup_no_result_message</source>
<extracomment>No result found</extracomment>
<translation>Aucun résultat trouvé</translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="51"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="52"/>
<source>info_popup_first_result_message</source>
<extracomment>First result reached</extracomment>
<translation>Premier résultat atteint</translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="53"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="54"/>
<source>info_popup_last_result_message</source>
<extracomment>Last result reached</extracomment>
<translation>Dernier résultat atteint</translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="140"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="143"/>
<source>chat_message_list_encrypted_header_title</source>
<extracomment>End to end encrypted chat</extracomment>
<translation>Conversation chiffrée de bout en bout</translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="150"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="153"/>
<source>chat_message_list_encrypted_header_message</source>
<extracomment>Les messages de cette conversation sont chiffrés de bout
en bout. Seul votre correspondant peut les déchiffrer.</extracomment>
@ -2073,7 +2073,7 @@ Error</extracomment>
en bout. Seul votre correspondant peut les déchiffrer.</translation>
</message>
<message>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="296"/>
<location filename="../../view/Control/Display/Chat/ChatMessagesListView.qml" line="191"/>
<source>chat_message_is_writing_info</source>
<extracomment>%1 is writing</extracomment>
<translation>%1 est en train d&apos;écrire</translation>

View file

@ -13,6 +13,7 @@ ListView {
property ChatGui chat
property color backgroundColor
property bool lastItemVisible: false
verticalLayoutDirection: ListView.BottomToTop
signal showReactionsForMessageRequested(ChatMessageGui chatMessage)
signal showImdnStatusForMessageRequested(ChatMessageGui chatMessage)
signal replyToMessageRequested(ChatMessageGui chatMessage)
@ -57,7 +58,7 @@ ListView {
Component.onCompleted: {
Qt.callLater(function() {
var index = eventLogProxy.findFirstUnreadIndex()
positionViewAtIndex(index, ListView.End)
positionViewAtIndex(index, ListView.Beginning)
eventLogProxy.markIndexAsRead(index)
})
}
@ -81,43 +82,46 @@ ListView {
anchors.rightMargin: Math.round(18 * DefaultStyle.dp)
onClicked: {
var index = eventLogProxy.findFirstUnreadIndex()
mainItem.positionViewAtIndex(index, ListView.End)
mainItem.positionViewAtIndex(index, ListView.Beginning)
eventLogProxy.markIndexAsRead(index)
}
}
onAtYEndChanged: if (atYEnd) {
chat.core.lMarkAsRead()
}
onAtYBeginningChanged: if (atYBeginning) {
if (eventLogProxy.haveMore)
eventLogProxy.displayMore()
else chat.core.lMarkAsRead()
}
model: EventLogProxy {
id: eventLogProxy
chatGui: mainItem.chat
// scroll when in view and message inserted
filterText: mainItem.filterText
initialDisplayItems: 10
displayItemsStep: 3 * initialDisplayItems / 2
onEventInserted: (index, gui) => {
if (!mainItem.visible) return
if(mainItem.lastItemVisible) mainItem.positionViewAtIndex(index, ListView.End)
if(mainItem.lastItemVisible) mainItem.positionViewAtIndex(index, ListView.Beginning)
}
onModelReset: Qt.callLater(function() {
var index = eventLogProxy.findFirstUnreadIndex()
positionViewAtIndex(index, ListView.End)
positionViewAtIndex(index, ListView.Beginning)
eventLogProxy.markIndexAsRead(index)
})
}
header: Item {
visible: mainItem.chat && mainItem.chat.core.isEncrypted
height: visible ? headerMessage.height + Math.round(50 * DefaultStyle.dp) : Math.round(30 * DefaultStyle.dp)
footer: Item {
visible: mainItem.chat && mainItem.chat.core.isEncrypted && !eventLogProxy.haveMore
height: visible ? headerMessage.height + headerMessage.topMargin + headerMessage.bottomMargin : Math.round(30 * DefaultStyle.dp)
width: headerMessage.width
anchors.horizontalCenter: parent.horizontalCenter
Control.Control {
id: headerMessage
anchors.topMargin: Math.round(30 * DefaultStyle.dp)
property int topMargin: mainItem.contentHeight > mainItem.height ? Math.round(30 * DefaultStyle.dp) : Math.round(50 * DefaultStyle.dp)
property int bottomMargin: Math.round(30 * DefaultStyle.dp)
anchors.topMargin: topMargin
anchors.bottomMargin: bottomMargin
anchors.top: parent.top
padding: Math.round(10 * DefaultStyle.dp)
background: Rectangle {
@ -159,118 +163,12 @@ ListView {
}
}
}
delegate: DelegateChooser {
role: "eventType"
DelegateChoice {
roleValue: "chatMessage"
delegate: ChatMessage {
id: chatMessageDelegate
property int yoff: Math.round(chatMessageDelegate.y - mainItem.contentY)
property bool isFullyVisible: (yoff > mainItem.y && yoff + height < mainItem.y + mainItem.height)
chatMessage: modelData.core.chatMessageGui
onIsFullyVisibleChanged: {
if (index === mainItem.count - 1) {
mainItem.lastItemVisible = isFullyVisible
}
}
Component.onCompleted: if (index === mainItem.count - 1) mainItem.lastItemVisible = isFullyVisible
chat: mainItem.chat
searchedTextPart: mainItem.filterText
maxWidth: Math.round(mainItem.width * (3/4))
width: mainItem.width
property var previousIndex: index - 1
property ChatMessageGui nextChatMessage: index >= (mainItem.count - 1)
? null
: eventLogProxy.getEventAtIndex(index+1)
? eventLogProxy.getEventAtIndex(index+1).core.chatMessageGui
: null
property var previousFromAddress: eventLogProxy.getEventAtIndex(index-1)?.core.chatMessage?.fromAddress
backgroundColor: isRemoteMessage ? DefaultStyle.main2_100 : DefaultStyle.main1_100
isFirstMessage: !previousFromAddress || previousFromAddress !== chatMessage.core.fromAddress
anchors.right: !isRemoteMessage && parent
? parent.right
: undefined
onMessageDeletionRequested: chatMessage.core.lDelete()
onShowReactionsForMessageRequested: mainItem.showReactionsForMessageRequested(chatMessage)
onShowImdnStatusForMessageRequested: mainItem.showImdnStatusForMessageRequested(chatMessage)
onReplyToMessageRequested: mainItem.replyToMessageRequested(chatMessage)
onForwardMessageRequested: mainItem.forwardMessageRequested(chatMessage)
onEndOfVoiceRecordingReached: {
if (nextChatMessage && nextChatMessage.core.isVoiceRecording) mainItem.requestAutoPlayVoiceRecording(index + 1)
}
Connections {
target: mainItem
function onRequestHighlight(indexToHighlight) {
if (indexToHighlight === index) {
requestHighlight()
}
}
function onRequestAutoPlayVoiceRecording(indexToPlay) {
if (indexToPlay === index) {
chatMessageDelegate.requestAutoPlayVoiceRecording()
}
}
}
}
}
DelegateChoice {
roleValue: "event"
delegate: Item {
id: eventDelegate
property int yoff: Math.round(eventDelegate.y - mainItem.contentY)
property bool isFullyVisible: (yoff > mainItem.y && yoff + height < mainItem.y + mainItem.height)
onIsFullyVisibleChanged: {
if (index === mainItem.count - 1) {
mainItem.lastItemVisible = isFullyVisible
}
}
property bool showTopMargin: !header.visible && index == 0
width: mainItem.width
height: (showTopMargin ? 30 : 0 * DefaultStyle.dp) + eventItem.implicitHeight
Event {
id: eventItem
anchors.top: parent.top
anchors.topMargin: showTopMargin ? 30 : 0 * DefaultStyle.dp
width: parent.width
eventLogGui: modelData
}
}
}
DelegateChoice {
roleValue: "ephemeralEvent"
delegate: Item {
id: ephemeralEventDelegate
property int yoff: Math.round(ephemeralEventDelegate.y - mainItem.contentY)
property bool isFullyVisible: (yoff > mainItem.y && yoff + height < mainItem.y + mainItem.height)
onIsFullyVisibleChanged: {
if (index === mainItem.count - 1) {
mainItem.lastItemVisible = isFullyVisible
}
}
property bool showTopMargin: !header.visible && index == 0
width: mainItem.width
//height: 40 * DefaultStyle.dp
height: (showTopMargin ? 30 : 0 * DefaultStyle.dp) + ephemeralEventItem.height
EphemeralEvent {
id: ephemeralEventItem
anchors.top: parent.top
anchors.topMargin: showTopMargin ? 30 : 0 * DefaultStyle.dp
eventLogGui: modelData
}
}
}
}
footerPositioning: ListView.OverlayFooter
footer: Control.Control {
footerPositioning: ListView.PullBackFooter
headerPositioning: ListView.OverlayHeader
header: Control.Control {
visible: composeLayout.composingName !== "" && composeLayout.composingName !== undefined
width: mainItem.width
// height: visible ? contentItem.implicitHeight + topPadding + bottomPadding : 0
z: mainItem.z + 2
topPadding: Math.round(5 * DefaultStyle.dp)
bottomPadding: Math.round(5 * DefaultStyle.dp)
@ -297,4 +195,112 @@ ListView {
}
}
}
delegate: DelegateChooser {
role: "eventType"
DelegateChoice {
roleValue: "chatMessage"
delegate: ChatMessage {
id: chatMessageDelegate
property int yoff: Math.round(chatMessageDelegate.y - mainItem.contentY)
property bool isFullyVisible: (yoff > mainItem.y && yoff + height < mainItem.y + mainItem.height)
chatMessage: modelData.core.chatMessageGui
onIsFullyVisibleChanged: {
if (index === 0) {
mainItem.lastItemVisible = isFullyVisible
}
}
Component.onCompleted: {
if (index === 0) mainItem.lastItemVisible = isFullyVisible
}
chat: mainItem.chat
searchedTextPart: mainItem.filterText
maxWidth: Math.round(mainItem.width * (3/4))
width: mainItem.width
property var previousIndex: index - 1
property ChatMessageGui nextChatMessage: index <= 0
? null
: eventLogProxy.getEventAtIndex(index-1)
? eventLogProxy.getEventAtIndex(index-1).core.chatMessageGui
: null
property var previousFromAddress: eventLogProxy.getEventAtIndex(index+1)?.core.chatMessage?.fromAddress
backgroundColor: isRemoteMessage ? DefaultStyle.main2_100 : DefaultStyle.main1_100
isFirstMessage: !previousFromAddress || previousFromAddress !== chatMessage.core.fromAddress
anchors.right: !isRemoteMessage && parent
? parent.right
: undefined
onMessageDeletionRequested: chatMessage.core.lDelete()
onShowReactionsForMessageRequested: mainItem.showReactionsForMessageRequested(chatMessage)
onShowImdnStatusForMessageRequested: mainItem.showImdnStatusForMessageRequested(chatMessage)
onReplyToMessageRequested: mainItem.replyToMessageRequested(chatMessage)
onForwardMessageRequested: mainItem.forwardMessageRequested(chatMessage)
onEndOfVoiceRecordingReached: {
if (nextChatMessage && nextChatMessage.core.isVoiceRecording) mainItem.requestAutoPlayVoiceRecording(index - 1)
}
Connections {
target: mainItem
function onRequestHighlight(indexToHighlight) {
if (indexToHighlight === index) {
requestHighlight()
}
}
function onRequestAutoPlayVoiceRecording(indexToPlay) {
if (indexToPlay === index) {
chatMessageDelegate.requestAutoPlayVoiceRecording()
}
}
}
}
}
DelegateChoice {
roleValue: "event"
delegate: Item {
id: eventDelegate
property int yoff: Math.round(eventDelegate.y - mainItem.contentY)
property bool isFullyVisible: (yoff > mainItem.y && yoff + height < mainItem.y + mainItem.height)
onIsFullyVisibleChanged: {
if (index === 0) {
mainItem.lastItemVisible = isFullyVisible
}
}
property bool showTopMargin: !header.visible && index == 0
width: mainItem.width
height: (showTopMargin ? 30 * DefaultStyle.dp : 0) + eventItem.implicitHeight
Event {
id: eventItem
anchors.top: parent.top
anchors.topMargin: showTopMargin ? 30 : 0 * DefaultStyle.dp
width: parent.width
eventLogGui: modelData
}
}
}
DelegateChoice {
roleValue: "ephemeralEvent"
delegate: Item {
id: ephemeralEventDelegate
property int yoff: Math.round(ephemeralEventDelegate.y - mainItem.contentY)
property bool isFullyVisible: (yoff > mainItem.y && yoff + height < mainItem.y + mainItem.height)
onIsFullyVisibleChanged: {
if (index === 0) {
mainItem.lastItemVisible = isFullyVisible
}
}
property bool showTopMargin: !header.visible && index == 0
width: mainItem.width
//height: 40 * DefaultStyle.dp
height: (showTopMargin ? 30 : 0 * DefaultStyle.dp) + ephemeralEventItem.height
EphemeralEvent {
id: ephemeralEventItem
anchors.top: parent.top
anchors.topMargin: showTopMargin ? 30 : 0 * DefaultStyle.dp
eventLogGui: modelData
}
}
}
}
}