From 99b9a7753c3bf60cc4bf3841e092bb7c25632ae0 Mon Sep 17 00:00:00 2001 From: Julien Wadel Date: Thu, 16 Sep 2021 19:34:40 +0200 Subject: [PATCH] Upgrade search and message loading. More smart loading entries based on currently view. Add a spinner while loading/searching. --- CMakeLists.txt | 2 +- .../components/chat-room/ChatRoomModel.cpp | 3 +- .../components/chat-room/ChatRoomModel.hpp | 2 +- .../chat-room/ChatRoomProxyModel.cpp | 31 +- linphone-app/ui/modules/Linphone/Chat/Chat.js | 9 - .../ui/modules/Linphone/Chat/Chat.qml | 598 ++++++++++-------- .../ui/views/App/Main/Conversation.qml | 15 +- 7 files changed, 350 insertions(+), 310 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 24bdefe3d..7b6a3c8b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,7 +47,7 @@ if(WIN32) endif() elseif( APPLE ) if( NOT CMAKE_OSX_DEPLOYMENT_TARGET) - set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9") + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9" CACHE STRING "Minimum OS X deployment version") endif() endif() diff --git a/linphone-app/src/components/chat-room/ChatRoomModel.cpp b/linphone-app/src/components/chat-room/ChatRoomModel.cpp index ba6f1d425..e1d4a0308 100644 --- a/linphone-app/src/components/chat-room/ChatRoomModel.cpp +++ b/linphone-app/src/components/chat-room/ChatRoomModel.cpp @@ -773,7 +773,7 @@ void ChatRoomModel::initEntries(){ } } -void ChatRoomModel::loadMoreEntries(){ +int ChatRoomModel::loadMoreEntries(){ QList > entries; QList prepareEntries; // Get current event count for each type @@ -838,6 +838,7 @@ void ChatRoomModel::loadMoreEntries(){ emit layoutChanged(); updateLastUpdateTime(); } + return entries.size(); } //------------------------------------------------- diff --git a/linphone-app/src/components/chat-room/ChatRoomModel.hpp b/linphone-app/src/components/chat-room/ChatRoomModel.hpp index dd0798bfe..281c5b6fa 100644 --- a/linphone-app/src/components/chat-room/ChatRoomModel.hpp +++ b/linphone-app/src/components/chat-room/ChatRoomModel.hpp @@ -218,7 +218,7 @@ public: void compose (); void resetMessageCount (); Q_INVOKABLE void initEntries(); - Q_INVOKABLE void loadMoreEntries(); + Q_INVOKABLE int loadMoreEntries(); // return new entries count void callEnded(std::shared_ptr call); QDateTime mLastUpdateTime; diff --git a/linphone-app/src/components/chat-room/ChatRoomProxyModel.cpp b/linphone-app/src/components/chat-room/ChatRoomProxyModel.cpp index 492e0f5cf..b6198d4db 100644 --- a/linphone-app/src/components/chat-room/ChatRoomProxyModel.cpp +++ b/linphone-app/src/components/chat-room/ChatRoomProxyModel.cpp @@ -140,17 +140,14 @@ void ChatRoomProxyModel::compose (const QString& text) { void ChatRoomProxyModel::loadMoreEntries () { if(mChatRoomModel ) { - int count = rowCount(); - int parentCount = sourceModel()->rowCount(); - if (count == mMaxDisplayedEntries) - mMaxDisplayedEntries += EntriesChunkSize; - - if (count + 10 >= parentCount) // Magic number : try to load more entries if near to max event count - mChatRoomModel->loadMoreEntries(); - invalidateFilter(); - count = rowCount() - count; - if (count > 0) - emit moreEntriesLoaded(count); + int currentRowCount = rowCount(); + int newEntries = 0; + do{ + newEntries = mChatRoomModel->loadMoreEntries(); + invalidate(); + }while( newEntries>0 && currentRowCount == rowCount()); + currentRowCount = rowCount() - currentRowCount + 1; + emit moreEntriesLoaded(currentRowCount); } } @@ -262,7 +259,6 @@ QString ChatRoomProxyModel::getCachedText() const{ // ----------------------------------------------------------------------------- void ChatRoomProxyModel::reload (ChatRoomModel *chatRoomModel) { - mMaxDisplayedEntries = EntriesChunkSize; if (mChatRoomModel) { ChatRoomModel *ChatRoomModel = mChatRoomModel.get(); @@ -294,8 +290,13 @@ void ChatRoomProxyModel::resetMessageCount(){ void ChatRoomProxyModel::setFilterText(const QString& text){ if( mFilterText != text){ mFilterText = text; - invalidate(); - emit filterTextChanged(); + int currentRowCount = rowCount(); + int newEntries = 0; + do{ + newEntries = mChatRoomModel->loadMoreEntries(); + invalidate(); + emit filterTextChanged(); + }while( newEntries>0 && currentRowCount == rowCount()); } } @@ -336,7 +337,6 @@ void ChatRoomProxyModel::handleIsRemoteComposingChanged () { } void ChatRoomProxyModel::handleMessageReceived (const shared_ptr &) { - mMaxDisplayedEntries++; QWindow *window = getParentWindow(this); if (window && window->isActive()) @@ -344,5 +344,4 @@ void ChatRoomProxyModel::handleMessageReceived (const shared_ptr &) { - mMaxDisplayedEntries++; } diff --git a/linphone-app/ui/modules/Linphone/Chat/Chat.js b/linphone-app/ui/modules/Linphone/Chat/Chat.js index 97b3dc06e..86762daf5 100644 --- a/linphone-app/ui/modules/Linphone/Chat/Chat.js +++ b/linphone-app/ui/modules/Linphone/Chat/Chat.js @@ -34,14 +34,6 @@ function initView () { chat.bindToEnd = true } -function loadMoreEntries () { - if (chat.atYBeginning && !chat.tryToLoadMoreEntries) { - chat.tryToLoadMoreEntries = true - chat.positionViewAtBeginning() - container.proxyModel.loadMoreEntries() - } -} - function getComponentFromEntry (chatEntry) { if (chatEntry.fileContentModel && chatEntry.fileContentModel.name) { return 'FileMessage.qml' @@ -77,7 +69,6 @@ function handleFilesDropped (files) { function handleMoreEntriesLoaded (n) { chat.positionViewAtIndex(n - 1, QtQuick.ListView.Beginning) - chat.tryToLoadMoreEntries = false } function handleMovementEnded () { diff --git a/linphone-app/ui/modules/Linphone/Chat/Chat.qml b/linphone-app/ui/modules/Linphone/Chat/Chat.qml index 6f7434ec6..cb3bae55e 100644 --- a/linphone-app/ui/modules/Linphone/Chat/Chat.qml +++ b/linphone-app/ui/modules/Linphone/Chat/Chat.qml @@ -11,292 +11,330 @@ import 'Chat.js' as Logic // ============================================================================= Rectangle { - id: container - - property alias proxyModel: chat.model // ChatRoomProxyModel - - // --------------------------------------------------------------------------- - - signal messageToSend (string text) - - // --------------------------------------------------------------------------- - - color: ChatStyle.color - - ColumnLayout { - anchors.fill: parent - spacing: 0 - - ScrollableListView { - id: chat - - // ----------------------------------------------------------------------- - - property bool bindToEnd: false - property bool tryToLoadMoreEntries: true - //property var sipAddressObserver: SipAddressesModel.getSipAddressObserver(proxyModel.fullPeerAddress, proxyModel.fullLocalAddress) - - // ----------------------------------------------------------------------- - - Layout.fillHeight: true - Layout.fillWidth: true - - highlightFollowsCurrentItem: false - - section { - criteria: ViewSection.FullString - delegate: sectionHeading - property: '$sectionDate' - } - - // ----------------------------------------------------------------------- - - Component.onCompleted: Logic.initView() - - onContentYChanged: Logic.loadMoreEntries() - onMovementEnded: Logic.handleMovementEnded() - onMovementStarted: Logic.handleMovementStarted() - - // ----------------------------------------------------------------------- - - Connections { - target: proxyModel - - // When the view is changed (for example `Calls` -> `Messages`), - // the position is set at end and it can be possible to load - // more entries. - onEntryTypeFilterChanged: Logic.initView() - onMoreEntriesLoaded: Logic.handleMoreEntriesLoaded(n) - } - - // ----------------------------------------------------------------------- - // Heading. - // ----------------------------------------------------------------------- - - Component { - id: sectionHeading - - Item { - implicitHeight: container.height + ChatStyle.sectionHeading.bottomMargin - width: parent.width - - Borders { - id: container - - borderColor: ChatStyle.sectionHeading.border.color - bottomWidth: ChatStyle.sectionHeading.border.width - implicitHeight: text.contentHeight + - ChatStyle.sectionHeading.padding * 2 + - ChatStyle.sectionHeading.border.width * 2 - topWidth: ChatStyle.sectionHeading.border.width - width: parent.width - - Text { - id: text - - anchors.fill: parent - color: ChatStyle.sectionHeading.text.color - font { - bold: true - pointSize: ChatStyle.sectionHeading.text.pointSize - } - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - - // Cast section to integer because Qt converts the - // sectionDate in string!!! - text: new Date(section).toLocaleDateString( - Qt.locale(App.locale) - ) - } - } - } - } - - // ----------------------------------------------------------------------- - // Message/Event renderer. - // ----------------------------------------------------------------------- - - delegate: Rectangle { - id: entry - property bool isNotice : $chatEntry.type === ChatRoomModel.NoticeEntry - property bool isCall : $chatEntry.type === ChatRoomModel.CallEntry - property bool isMessage : $chatEntry.type === ChatRoomModel.MessageEntry - - function isHoverEntry () { - return mouseArea.containsMouse - } - - function removeEntry () { - proxyModel.removeRow(index) - } - - anchors { - left: parent ? parent.left : undefined - leftMargin: isNotice?0:ChatStyle.entry.leftMargin - right: parent ? parent.right : undefined - - rightMargin: isNotice?0:ChatStyle.entry.deleteIconSize + - ChatStyle.entry.message.extraContent.spacing + - ChatStyle.entry.message.extraContent.rightMargin + - ChatStyle.entry.message.extraContent.leftMargin + - ChatStyle.entry.message.outgoing.areaSize - } - - color: ChatStyle.color - implicitHeight: layout.height + ChatStyle.entry.bottomMargin - - // --------------------------------------------------------------------- - - MouseArea { - id: mouseArea - - cursorShape: Qt.ArrowCursor - hoverEnabled: true - implicitHeight: layout.height - width: parent.width + parent.anchors.rightMargin - acceptedButtons: Qt.NoButton - ColumnLayout{ - id: layout - spacing: 0 - width: entry.width - Text{ - id:authorName - Layout.leftMargin: timeDisplay.width + 10 - Layout.fillWidth: true - text : $chatEntry.fromDisplayName ? $chatEntry.fromDisplayName : '' - property var previousItem : { - if(index >0) - return proxyModel.getAt(index-1) - else - return null - } + id: container + + property alias proxyModel: chat.model // ChatRoomProxyModel + property alias tryingToLoadMoreEntries : chat.tryToLoadMoreEntries + + // --------------------------------------------------------------------------- + + signal messageToSend (string text) + + // --------------------------------------------------------------------------- + + color: ChatStyle.color + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + ScrollableListView { + id: chat + + // ----------------------------------------------------------------------- + + property bool bindToEnd: false + property bool tryToLoadMoreEntries: true + //property var sipAddressObserver: SipAddressesModel.getSipAddressObserver(proxyModel.fullPeerAddress, proxyModel.fullLocalAddress) + + // ----------------------------------------------------------------------- + Layout.fillHeight: true + Layout.fillWidth: true + + highlightFollowsCurrentItem: false + + section { + criteria: ViewSection.FullString + delegate: sectionHeading + property: '$sectionDate' + } + + Timer { + id: loadMoreEntriesDelayer + interval: 1 + repeat: false + running: false + + onTriggered: { + chat.positionViewAtBeginning() + container.proxyModel.loadMoreEntries() + } + } + Timer { + // Delay each search by 100ms + id: endOfLoadMoreEntriesDelayer + interval: 100 + repeat: false + running: false + + onTriggered: { + if(chat.atYBeginning){// We are still at the beginning. Try to continue searching + loadMoreEntriesDelayer.start() + }else// We are not at the begining. New search can be done by moving to the top. + chat.tryToLoadMoreEntries = false + } + } + + // ----------------------------------------------------------------------- + + Component.onCompleted: Logic.initView() + + onContentYChanged: { + if (chat.atYBeginning && !chat.tryToLoadMoreEntries) { + chat.tryToLoadMoreEntries = true// Show busy indicator + loadMoreEntriesDelayer.start()// Let GUI time to the busy indicator to be shown + } + } + onMovementEnded: Logic.handleMovementEnded() + onMovementStarted: Logic.handleMovementStarted() + + // ----------------------------------------------------------------------- + + Connections { + target: proxyModel + + // When the view is changed (for example `Calls` -> `Messages`), + // the position is set at end and it can be possible to load + // more entries. + onEntryTypeFilterChanged: Logic.initView() + onMoreEntriesLoaded: { + Logic.handleMoreEntriesLoaded(n) + if(n>1)// New entries : delay the end + endOfLoadMoreEntriesDelayer.start() + else// No new entries, we can stop without waiting + chat.tryToLoadMoreEntries = false + } + } + + // ----------------------------------------------------------------------- + // Heading. + // ----------------------------------------------------------------------- + + Component { + id: sectionHeading + + Item { + implicitHeight: container.height + ChatStyle.sectionHeading.bottomMargin + width: parent.width - color: ChatStyle.entry.event.text.color - font.pointSize: ChatStyle.entry.event.text.pointSize - visible: isMessage - && $chatEntry != undefined - && !$chatEntry.isOutgoing // Only outgoing - && (!previousItem //No previous entry - || previousItem.type != ChatRoomModel.MessageEntry // Previous entry is a message - || previousItem.fromSipAddress != $chatEntry.fromSipAddress // Different user - || (new Date(previousItem.timestamp)).setHours(0, 0, 0, 0) != (new Date($chatEntry.timestamp)).setHours(0, 0, 0, 0) // Same day == section - ) + Borders { + id: container + + borderColor: ChatStyle.sectionHeading.border.color + bottomWidth: ChatStyle.sectionHeading.border.width + implicitHeight: text.contentHeight + + ChatStyle.sectionHeading.padding * 2 + + ChatStyle.sectionHeading.border.width * 2 + topWidth: ChatStyle.sectionHeading.border.width + width: parent.width + + Text { + id: text + + anchors.fill: parent + color: ChatStyle.sectionHeading.text.color + font { + bold: true + pointSize: ChatStyle.sectionHeading.text.pointSize + } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + // Cast section to integer because Qt converts the + // sectionDate in string!!! + text: new Date(section).toLocaleDateString( + Qt.locale(App.locale) + ) + } + } } - RowLayout { - - spacing: 0 - width: entry.width - - // Display time. - Text { - id:timeDisplay - Layout.alignment: Qt.AlignTop - Layout.preferredHeight: ChatStyle.entry.lineHeight - Layout.preferredWidth: ChatStyle.entry.time.width - - color: ChatStyle.entry.event.text.color - font.pointSize: ChatStyle.entry.time.pointSize - - text: $chatEntry.timestamp.toLocaleString( - Qt.locale(App.locale), - 'hh:mm' - ) - - verticalAlignment: Text.AlignVCenter - - TooltipArea { - text: $chatEntry.timestamp.toLocaleString(Qt.locale(App.locale)) - } - visible:!isNotice + } + + // ----------------------------------------------------------------------- + // Message/Event renderer. + // ----------------------------------------------------------------------- + + delegate: Rectangle { + id: entry + property bool isNotice : $chatEntry.type === ChatRoomModel.NoticeEntry + property bool isCall : $chatEntry.type === ChatRoomModel.CallEntry + property bool isMessage : $chatEntry.type === ChatRoomModel.MessageEntry + + function isHoverEntry () { + return mouseArea.containsMouse } - - // Display content. - Loader { - Layout.fillWidth: true - source: Logic.getComponentFromEntry($chatEntry) + + function removeEntry () { + proxyModel.removeRow(index) } - } - } - } - } - - footer: Text { - property var composers : container.proxyModel.composers - color: ChatStyle.composingText.color - font.pointSize: ChatStyle.composingText.pointSize - height: visible ? undefined : 0 - leftPadding: ChatStyle.composingText.leftPadding - visible: composers.length > 0 && SettingsModel.chatEnabled - wrapMode: Text.Wrap - //: '%1 is typing...' indicate that someone is composing in chat - text:(composers.length==0?'': qsTr('chatTyping','',composers.length).arg(container.proxyModel.getDisplayNameComposers())) + + anchors { + left: parent ? parent.left : undefined + leftMargin: isNotice?0:ChatStyle.entry.leftMargin + right: parent ? parent.right : undefined + + rightMargin: isNotice?0:ChatStyle.entry.deleteIconSize + + ChatStyle.entry.message.extraContent.spacing + + ChatStyle.entry.message.extraContent.rightMargin + + ChatStyle.entry.message.extraContent.leftMargin + + ChatStyle.entry.message.outgoing.areaSize } - } - - // ------------------------------------------------------------------------- - // Send area. - // ------------------------------------------------------------------------- - - Borders { - Layout.fillWidth: true - Layout.preferredHeight: textArea.height - - borderColor: ChatStyle.sendArea.border.color - topWidth: ChatStyle.sendArea.border.width - visible: SettingsModel.chatEnabled && proxyModel.chatRoomModel && !proxyModel.chatRoomModel.hasBeenLeft - - DroppableTextArea { - id: textArea - - enabled:proxyModel && proxyModel.chatRoomModel ? !proxyModel.chatRoomModel.hasBeenLeft:false - isEphemeral : proxyModel && proxyModel.chatRoomModel ? proxyModel.chatRoomModel.ephemeralEnabled:false - - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - - height:ChatStyle.sendArea.height + ChatStyle.sendArea.border.width - minimumHeight:ChatStyle.sendArea.height + ChatStyle.sendArea.border.width - maximumHeight:container.height/2 - - dropEnabled: SettingsModel.fileTransferUrl.length > 0 - dropDisabledReason: qsTr('noFileTransferUrl') - placeholderText: qsTr('newMessagePlaceholder') - - onDropped: Logic.handleFilesDropped(files) - onTextChanged: Logic.handleTextChanged(text) - onValidText: { - textArea.text = '' - chat.bindToEnd = true - if(proxyModel.chatRoomModel) - proxyModel.sendMessage(text) - else{ - console.log("Peer : " +proxyModel.peerAddress+ "/"+chat.model.peerAddress) - proxyModel.chatRoomModel = CallsListModel.createChat(proxyModel.peerAddress) - proxyModel.sendMessage(text) + + color: ChatStyle.color + implicitHeight: layout.height + ChatStyle.entry.bottomMargin + + // --------------------------------------------------------------------- + + MouseArea { + id: mouseArea + + cursorShape: Qt.ArrowCursor + hoverEnabled: true + implicitHeight: layout.height + width: parent.width + parent.anchors.rightMargin + acceptedButtons: Qt.NoButton + ColumnLayout{ + id: layout + spacing: 0 + width: entry.width + Text{ + id:authorName + Layout.leftMargin: timeDisplay.width + 10 + Layout.fillWidth: true + text : $chatEntry.fromDisplayName ? $chatEntry.fromDisplayName : '' + property var previousItem : { + if(index >0) + return proxyModel.getAt(index-1) + else + return null + } + + color: ChatStyle.entry.event.text.color + font.pointSize: ChatStyle.entry.event.text.pointSize + visible: isMessage + && $chatEntry != undefined + && !$chatEntry.isOutgoing // Only outgoing + && (!previousItem //No previous entry + || previousItem.type != ChatRoomModel.MessageEntry // Previous entry is a message + || previousItem.fromSipAddress != $chatEntry.fromSipAddress // Different user + || (new Date(previousItem.timestamp)).setHours(0, 0, 0, 0) != (new Date($chatEntry.timestamp)).setHours(0, 0, 0, 0) // Same day == section + ) + } + RowLayout { + + spacing: 0 + width: entry.width + + // Display time. + Text { + id:timeDisplay + Layout.alignment: Qt.AlignTop + Layout.preferredHeight: ChatStyle.entry.lineHeight + Layout.preferredWidth: ChatStyle.entry.time.width + + color: ChatStyle.entry.event.text.color + font.pointSize: ChatStyle.entry.time.pointSize + + text: $chatEntry.timestamp.toLocaleString( + Qt.locale(App.locale), + 'hh:mm' + ) + + verticalAlignment: Text.AlignVCenter + + TooltipArea { + text: $chatEntry.timestamp.toLocaleString(Qt.locale(App.locale)) + } + visible:!isNotice + } + + // Display content. + Loader { + Layout.fillWidth: true + source: Logic.getComponentFromEntry($chatEntry) + } + } + } + } + } + + footer: Text { + property var composers : container.proxyModel.composers + color: ChatStyle.composingText.color + font.pointSize: ChatStyle.composingText.pointSize + height: visible ? undefined : 0 + leftPadding: ChatStyle.composingText.leftPadding + visible: composers.length > 0 && SettingsModel.chatEnabled + wrapMode: Text.Wrap + //: '%1 is typing...' indicate that someone is composing in chat + text:(composers.length==0?'': qsTr('chatTyping','',composers.length).arg(container.proxyModel.getDisplayNameComposers())) } } - Component.onCompleted: {text = proxyModel.cachedText; cursorPosition=text.length} - Rectangle{ - anchors.fill:parent - color:'white' - opacity: 0.5 - visible:!textArea.enabled + + // ------------------------------------------------------------------------- + // Send area. + // ------------------------------------------------------------------------- + + Borders { + Layout.fillWidth: true + Layout.preferredHeight: textArea.height + + borderColor: ChatStyle.sendArea.border.color + topWidth: ChatStyle.sendArea.border.width + visible: SettingsModel.chatEnabled && proxyModel.chatRoomModel && !proxyModel.chatRoomModel.hasBeenLeft + + DroppableTextArea { + id: textArea + + enabled:proxyModel && proxyModel.chatRoomModel ? !proxyModel.chatRoomModel.hasBeenLeft:false + isEphemeral : proxyModel && proxyModel.chatRoomModel ? proxyModel.chatRoomModel.ephemeralEnabled:false + + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + + height:ChatStyle.sendArea.height + ChatStyle.sendArea.border.width + minimumHeight:ChatStyle.sendArea.height + ChatStyle.sendArea.border.width + maximumHeight:container.height/2 + + dropEnabled: SettingsModel.fileTransferUrl.length > 0 + dropDisabledReason: qsTr('noFileTransferUrl') + placeholderText: qsTr('newMessagePlaceholder') + + onDropped: Logic.handleFilesDropped(files) + onTextChanged: Logic.handleTextChanged(text) + onValidText: { + textArea.text = '' + chat.bindToEnd = true + if(proxyModel.chatRoomModel) + proxyModel.sendMessage(text) + else{ + console.log("Peer : " +proxyModel.peerAddress+ "/"+chat.model.peerAddress) + proxyModel.chatRoomModel = CallsListModel.createChat(proxyModel.peerAddress) + proxyModel.sendMessage(text) + } + } + Component.onCompleted: {text = proxyModel.cachedText; cursorPosition=text.length} + Rectangle{ + anchors.fill:parent + color:'white' + opacity: 0.5 + visible:!textArea.enabled + } + } } - } - } - } - - // --------------------------------------------------------------------------- - // Scroll at end if necessary. - // --------------------------------------------------------------------------- - - Timer { - interval: 100 - repeat: true - running: true - - onTriggered: chat.bindToEnd && chat.positionViewAtEnd() - } + } + + // --------------------------------------------------------------------------- + // Scroll at end if necessary. + // --------------------------------------------------------------------------- + + Timer { + interval: 100 + repeat: true + running: true + + onTriggered: chat.bindToEnd && chat.positionViewAtEnd() + } + } diff --git a/linphone-app/ui/views/App/Main/Conversation.qml b/linphone-app/ui/views/App/Main/Conversation.qml index b18b64aa8..a8068e8ca 100644 --- a/linphone-app/ui/views/App/Main/Conversation.qml +++ b/linphone-app/ui/views/App/Main/Conversation.qml @@ -414,6 +414,17 @@ ColumnLayout { onClicked: Logic.updateChatFilter(button) } + BusyIndicator{ + id: chatLoading + width: 20 + height: 20 + anchors.left: filterButtons.right + anchors.leftMargin: 50 + anchors.verticalCenter: parent.verticalCenter + //anchors.horizontalCenter: parent.horizontalCenter + visible: chatArea.tryingToLoadMoreEntries + } + // ------------------------------------------------------------------------- // Search. // ------------------------------------------------------------------------- @@ -422,9 +433,9 @@ ColumnLayout { anchors.right: parent.right anchors.top: parent.top anchors.bottom: parent.bottom - anchors.left : filterButtons.right + anchors.left : chatLoading.right anchors.rightMargin: 10 - anchors.leftMargin: 80 + anchors.leftMargin: 50 anchors.topMargin: 10 anchors.bottomMargin: 10