mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-01-30 02:19:23 +00:00
- Shortcut in Reply to message's origin.
- Avoid to load huge chat room at the start of a call. - Asynchronous chat room load. - Fix video freeze on network change.
This commit is contained in:
parent
4ca37adc60
commit
33f382d80a
12 changed files with 140 additions and 81 deletions
|
|
@ -8,13 +8,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Added
|
||||
- Features:
|
||||
* messages features : Reply, forward (to contact, to a SIP address or to a timeline), Vocal record and play, multi contents.
|
||||
|
||||
* Messages features : Reply, forward (to contact, to a SIP address or to a timeline), Vocal record and play, multi contents.
|
||||
- Add a feedback on fetching remote provisioning when it failed.
|
||||
- Option to enable message notifications.
|
||||
- CPIM on basic chat rooms.
|
||||
- New event on new messages in chat and a shortcut to go to the end of chat if last message is not shown.
|
||||
- Device name can be changed from settings.
|
||||
- New event on new messages in chat and a shortcut to go to the end of chat if last message is not shown.
|
||||
- Shortcut in Reply to message's origin.
|
||||
- Based on Linphone SDK 5.1
|
||||
|
||||
### Fixed
|
||||
|
|
@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Take account of return key on Numpad
|
||||
- Huge messages are better shown and with less flickering.
|
||||
- Adapt UserAgent with device name.
|
||||
- Video freeze on network change.
|
||||
|
||||
## 4.3.2
|
||||
|
||||
|
|
|
|||
|
|
@ -881,12 +881,48 @@ void ChatRoomModel::updateNewMessageNotice(const int& count){
|
|||
beginInsertRows(QModelIndex(), 0, 0);
|
||||
mEntries.prepend(mUnreadMessageNotice);
|
||||
endInsertRows();
|
||||
qWarning() << "New message notice timestamp to :" << lastUnreadMessage.toString();
|
||||
qDebug() << "New message notice timestamp to :" << lastUnreadMessage.toString();
|
||||
}
|
||||
//emit layoutChanged();
|
||||
}
|
||||
}
|
||||
|
||||
int ChatRoomModel::loadTillMessage(ChatMessageModel * message){
|
||||
if( message){
|
||||
qDebug() << "Load history till message : " << message->getChatMessage()->getMessageId().c_str();
|
||||
auto linphoneMessage = message->getChatMessage();
|
||||
// First find on current list
|
||||
auto entry = std::find_if(mEntries.begin(), mEntries.end(), [linphoneMessage](const std::shared_ptr<ChatEvent>& entry ){
|
||||
return entry->mType == ChatRoomModel::EntryType::MessageEntry && dynamic_cast<ChatMessageModel*>(entry.get())->getChatMessage() == linphoneMessage;
|
||||
});
|
||||
// if not find, load more entries and find it in new entries.
|
||||
if( entry == mEntries.end()){
|
||||
int newEntries = loadMoreEntries();
|
||||
while( newEntries > 0){// no more new entries
|
||||
int entryCount = 0;
|
||||
entry = mEntries.begin();
|
||||
while(entryCount < newEntries &&
|
||||
((*entry)->mType != ChatRoomModel::EntryType::MessageEntry || dynamic_cast<ChatMessageModel*>(entry->get())->getChatMessage() != linphoneMessage)
|
||||
){
|
||||
++entryCount;
|
||||
++entry;
|
||||
}
|
||||
if( entryCount < newEntries){// We got it
|
||||
qDebug() << "Find message at " << entryCount << " after loading new entries";
|
||||
return entryCount;
|
||||
}else
|
||||
newEntries = loadMoreEntries();// continue
|
||||
}
|
||||
}else{
|
||||
int entryCount = entry - mEntries.begin();
|
||||
qDebug() << "Find message at " << entryCount;
|
||||
return entryCount;
|
||||
}
|
||||
qWarning() << "Message has not been found in history";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ChatRoomModel::initEntries(){
|
||||
qDebug() << "Internal Entries : Init";
|
||||
// On call : reinitialize all entries. This allow to free up memory
|
||||
|
|
|
|||
|
|
@ -97,7 +97,6 @@ signals:
|
|||
void chatMessageShouldBeStored(const std::shared_ptr<linphone::ChatRoom> & chatRoom, const std::shared_ptr<linphone::ChatMessage> & message);
|
||||
void chatMessageParticipantImdnStateChanged(const std::shared_ptr<linphone::ChatRoom> & chatRoom, const std::shared_ptr<linphone::ChatMessage> & message, const std::shared_ptr<const linphone::ParticipantImdnState> & state);
|
||||
|
||||
|
||||
};
|
||||
|
||||
class ChatRoomModel : public QAbstractListModel {
|
||||
|
|
@ -119,9 +118,6 @@ public:
|
|||
};
|
||||
Q_ENUM(EntryType)
|
||||
|
||||
|
||||
//Q_PROPERTY(QString participants READ getParticipants NOTIFY participantsChanged);
|
||||
//Q_PROPERTY(ParticipantProxyModel participants READ getParticipants NOTIFY participantsChanged);
|
||||
Q_PROPERTY(QString subject READ getSubject WRITE setSubject NOTIFY subjectChanged)
|
||||
Q_PROPERTY(QDateTime lastUpdateTime MEMBER mLastUpdateTime WRITE setLastUpdateTime NOTIFY lastUpdateTimeChanged)
|
||||
Q_PROPERTY(int unreadMessagesCount MEMBER mUnreadMessagesCount WRITE setUnreadMessagesCount NOTIFY unreadMessagesCountChanged)
|
||||
|
|
@ -238,6 +234,7 @@ public:
|
|||
Q_INVOKABLE int loadMoreEntries(); // return new entries count
|
||||
void callEnded(std::shared_ptr<linphone::Call> call);
|
||||
void updateNewMessageNotice(const int& count);
|
||||
Q_INVOKABLE int loadTillMessage(ChatMessageModel * message);// Load all entries till message and return its index. -1 if not found.
|
||||
|
||||
QDateTime mLastUpdateTime;
|
||||
int mUnreadMessagesCount = 0;
|
||||
|
|
|
|||
|
|
@ -38,47 +38,10 @@ using namespace std;
|
|||
|
||||
QString ChatRoomProxyModel::gCachedText;
|
||||
|
||||
// Fetch the L last filtered chat entries.
|
||||
class ChatRoomProxyModel::ChatRoomModelFilter : public QSortFilterProxyModel {
|
||||
public:
|
||||
ChatRoomModelFilter (QObject *parent) : QSortFilterProxyModel(parent) {}
|
||||
|
||||
int getEntryTypeFilter () {
|
||||
return mEntryTypeFilter;
|
||||
}
|
||||
|
||||
void setEntryTypeFilter (int type) {
|
||||
mEntryTypeFilter = type;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow (int sourceRow, const QModelIndex &) const override {
|
||||
if (mEntryTypeFilter == ChatRoomModel::EntryType::GenericEntry)
|
||||
return true;
|
||||
|
||||
QModelIndex index = sourceModel()->index(sourceRow, 0, QModelIndex());
|
||||
auto eventModel = sourceModel()->data(index);
|
||||
|
||||
if( mEntryTypeFilter == ChatRoomModel::EntryType::CallEntry && eventModel.value<ChatCallModel*>() != nullptr)
|
||||
return true;
|
||||
if( mEntryTypeFilter == ChatRoomModel::EntryType::MessageEntry && eventModel.value<ChatMessageModel*>() != nullptr)
|
||||
return true;
|
||||
if( mEntryTypeFilter == ChatRoomModel::EntryType::NoticeEntry && eventModel.value<ChatNoticeModel*>() != nullptr)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
int mEntryTypeFilter = ChatRoomModel::EntryType::GenericEntry;
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
|
||||
ChatRoomProxyModel::ChatRoomProxyModel (QObject *parent) : QSortFilterProxyModel(parent) {
|
||||
setSourceModel(new ChatRoomModelFilter(this));
|
||||
mMarkAsReadEnabled = true;
|
||||
//mIsSecure = false;
|
||||
|
||||
App *app = App::getInstance();
|
||||
QObject::connect(app->getMainWindow(), &QWindow::activeChanged, this, [this]() {
|
||||
|
|
@ -109,13 +72,12 @@ ChatRoomProxyModel::ChatRoomProxyModel (QObject *parent) : QSortFilterProxyModel
|
|||
void ChatRoomProxyModel::METHOD (ARG_TYPE value) { \
|
||||
GET_CHAT_MODEL()->METHOD(value); \
|
||||
}
|
||||
|
||||
|
||||
#define CREATE_PARENT_MODEL_FUNCTION_WITH_ID(METHOD) \
|
||||
void ChatRoomProxyModel::METHOD (int id) { \
|
||||
QModelIndex sourceIndex = mapToSource(index(id, 0)); \
|
||||
GET_CHAT_MODEL()->METHOD( \
|
||||
static_cast<ChatRoomModelFilter *>(sourceModel())->mapToSource(sourceIndex).row() \
|
||||
); \
|
||||
GET_CHAT_MODEL()->METHOD( \
|
||||
mapFromSource(static_cast<ChatRoomModel*>(sourceModel())->index(id, 0)).row() \
|
||||
); \
|
||||
}
|
||||
|
||||
CREATE_PARENT_MODEL_FUNCTION(removeAllEntries)
|
||||
|
|
@ -139,6 +101,10 @@ void ChatRoomProxyModel::compose (const QString& text) {
|
|||
gCachedText = text;
|
||||
}
|
||||
|
||||
int ChatRoomProxyModel::getEntryTypeFilter () {
|
||||
return mEntryTypeFilter;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
void ChatRoomProxyModel::loadMoreEntriesAsync(){
|
||||
|
|
@ -155,10 +121,9 @@ void ChatRoomProxyModel::loadMoreEntries() {
|
|||
}
|
||||
|
||||
void ChatRoomProxyModel::setEntryTypeFilter (int type) {
|
||||
ChatRoomModelFilter *ChatRoomModelFilter = static_cast<ChatRoomProxyModel::ChatRoomModelFilter *>(sourceModel());
|
||||
|
||||
if (ChatRoomModelFilter->getEntryTypeFilter() != type) {
|
||||
ChatRoomModelFilter->setEntryTypeFilter(type);
|
||||
if (getEntryTypeFilter() != type) {
|
||||
mEntryTypeFilter = type;
|
||||
invalidate();
|
||||
emit entryTypeFilterChanged(type);
|
||||
}
|
||||
}
|
||||
|
|
@ -167,7 +132,21 @@ void ChatRoomProxyModel::setEntryTypeFilter (int type) {
|
|||
|
||||
bool ChatRoomProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex &sourceParent) const {
|
||||
bool show = false;
|
||||
if(mFilterText != ""){
|
||||
|
||||
if (mEntryTypeFilter == ChatRoomModel::EntryType::GenericEntry)
|
||||
show = true;
|
||||
else{
|
||||
QModelIndex index = sourceModel()->index(sourceRow, 0, QModelIndex());
|
||||
auto eventModel = sourceModel()->data(index);
|
||||
|
||||
if( mEntryTypeFilter == ChatRoomModel::EntryType::CallEntry && eventModel.value<ChatCallModel*>() != nullptr)
|
||||
show = true;
|
||||
else if( mEntryTypeFilter == ChatRoomModel::EntryType::MessageEntry && eventModel.value<ChatMessageModel*>() != nullptr)
|
||||
show = true;
|
||||
else if( mEntryTypeFilter == ChatRoomModel::EntryType::NoticeEntry && eventModel.value<ChatNoticeModel*>() != nullptr)
|
||||
show = true;
|
||||
}
|
||||
if( show && mFilterText != ""){
|
||||
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
auto eventModel = sourceModel()->data(index);
|
||||
ChatMessageModel * chatModel = eventModel.value<ChatMessageModel*>();
|
||||
|
|
@ -175,11 +154,10 @@ bool ChatRoomProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex &sou
|
|||
QRegularExpression search(QRegularExpression::escape(mFilterText), QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
|
||||
show = chatModel->mContent.contains(search);
|
||||
}
|
||||
}else
|
||||
show = true;
|
||||
|
||||
}
|
||||
return show;
|
||||
}
|
||||
|
||||
bool ChatRoomProxyModel::lessThan (const QModelIndex &left, const QModelIndex &right) const {
|
||||
auto l = sourceModel()->data(left);
|
||||
auto r = sourceModel()->data(right);
|
||||
|
|
@ -279,7 +257,6 @@ void ChatRoomProxyModel::reload (ChatRoomModel *chatRoomModel) {
|
|||
QObject::disconnect(ChatRoomModel, &ChatRoomModel::moreEntriesLoaded, this, &ChatRoomProxyModel::onMoreEntriesLoaded);
|
||||
}
|
||||
|
||||
|
||||
mChatRoomModel = CoreManager::getInstance()->getTimelineListModel()->getChatRoomModel(chatRoomModel);
|
||||
|
||||
if (mChatRoomModel) {
|
||||
|
|
@ -290,11 +267,12 @@ void ChatRoomProxyModel::reload (ChatRoomModel *chatRoomModel) {
|
|||
QObject::connect(ChatRoomModel, &ChatRoomModel::messageSent, this, &ChatRoomProxyModel::handleMessageSent);
|
||||
QObject::connect(ChatRoomModel, &ChatRoomModel::markAsReadEnabledChanged, this, &ChatRoomProxyModel::markAsReadEnabledChanged);
|
||||
QObject::connect(ChatRoomModel, &ChatRoomModel::moreEntriesLoaded, this, &ChatRoomProxyModel::onMoreEntriesLoaded);
|
||||
mChatRoomModel->initEntries();// This way, we don't load huge chat rooms (that lead to freeze GUI)
|
||||
}
|
||||
|
||||
static_cast<ChatRoomModelFilter *>(sourceModel())->setSourceModel(mChatRoomModel.get());
|
||||
setSourceModel(mChatRoomModel.get());
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void ChatRoomProxyModel::resetMessageCount(){
|
||||
if( mChatRoomModel){
|
||||
mChatRoomModel->resetMessageCount();
|
||||
|
|
@ -314,6 +292,15 @@ void ChatRoomProxyModel::setFilterText(const QString& text){
|
|||
}
|
||||
}
|
||||
|
||||
int ChatRoomProxyModel::loadTillMessage(ChatMessageModel * message){
|
||||
int messageIndex = mChatRoomModel->loadTillMessage(message);
|
||||
if( messageIndex>= 0 ) {
|
||||
messageIndex = mapFromSource(static_cast<ChatRoomModel*>(sourceModel())->index(messageIndex, 0)).row();
|
||||
}
|
||||
qDebug() << "Message index from chat room proxy : " << messageIndex;
|
||||
return messageIndex;
|
||||
}
|
||||
|
||||
ChatRoomModel *ChatRoomProxyModel::getChatRoomModel () const{
|
||||
return mChatRoomModel.get();
|
||||
|
||||
|
|
|
|||
|
|
@ -52,13 +52,15 @@ class ChatRoomProxyModel : public QSortFilterProxyModel {
|
|||
public:
|
||||
ChatRoomProxyModel (QObject *parent = Q_NULLPTR);
|
||||
|
||||
int getEntryTypeFilter ();
|
||||
Q_INVOKABLE void setEntryTypeFilter (int type);
|
||||
|
||||
Q_INVOKABLE QString getDisplayNameComposers()const;
|
||||
Q_INVOKABLE QVariant getAt(int row);
|
||||
|
||||
|
||||
Q_INVOKABLE void loadMoreEntriesAsync ();
|
||||
Q_INVOKABLE void loadMoreEntries ();
|
||||
Q_INVOKABLE void setEntryTypeFilter (int type);
|
||||
|
||||
Q_INVOKABLE void removeAllEntries ();
|
||||
Q_INVOKABLE void removeRow (int index);
|
||||
|
|
@ -75,6 +77,8 @@ public:
|
|||
|
||||
Q_INVOKABLE void setFilterText(const QString& text);
|
||||
|
||||
Q_INVOKABLE int loadTillMessage(ChatMessageModel * message);// Load all entries till message and return its index in displayed list (-1 if not found)
|
||||
|
||||
public slots:
|
||||
void onMoreEntriesLoaded(const int& count);
|
||||
|
||||
|
|
@ -133,6 +137,7 @@ private:
|
|||
void handleMessageSent (const std::shared_ptr<linphone::ChatMessage> &message);
|
||||
|
||||
int mMaxDisplayedEntries = EntriesChunkSize;
|
||||
int mEntryTypeFilter = ChatRoomModel::EntryType::GenericEntry;
|
||||
|
||||
QString mPeerAddress;
|
||||
QString mLocalAddress;
|
||||
|
|
|
|||
|
|
@ -99,7 +99,6 @@ void TimelineModel::setSelected(const bool& selected){
|
|||
<< ", isAdmin:"<< mChatRoomModel->isMeAdmin()
|
||||
<< ", canHandleParticipants:"<< mChatRoomModel->canHandleParticipants()
|
||||
<< ", hasBeenLeft:" << mChatRoomModel->hasBeenLeft();
|
||||
mChatRoomModel->initEntries();
|
||||
}
|
||||
emit selectedChanged(mSelected);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,15 @@ Rectangle {
|
|||
|
||||
color: ChatStyle.color
|
||||
|
||||
function positionViewAtIndex(index){
|
||||
chat.bindToEnd = false
|
||||
chat.positionViewAtIndex(index, ListView.Beginning)
|
||||
}
|
||||
|
||||
function goToMessage(message){
|
||||
positionViewAtIndex(container.proxyModel.loadTillMessage(message))
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
|
@ -41,13 +50,15 @@ Rectangle {
|
|||
// -----------------------------------------------------------------------
|
||||
property bool bindToEnd: false
|
||||
property bool displaying: false
|
||||
property int loaderCount: 0
|
||||
property int readyItems : 0
|
||||
property bool loadingLoader: (readyItems != loaderCount)
|
||||
property bool loadingEntries: container.proxyModel.chatRoomModel.entriesLoading || displaying
|
||||
property bool tryToLoadMoreEntries: loadingEntries || loadingLoader
|
||||
property bool loadingEntries: (container.proxyModel.chatRoomModel && container.proxyModel.chatRoomModel.entriesLoading) || displaying
|
||||
property bool tryToLoadMoreEntries: loadingEntries || remainingLoadersCount>0
|
||||
property bool isMoving : false // replace moving read-only property to allow using movement signals.
|
||||
|
||||
// Load optimizations
|
||||
property int remainingLoadersCount: 0
|
||||
property int syncLoaderBatch: 50 // batch of simultaneous loaders on synchronous mode
|
||||
//------------------------------------
|
||||
|
||||
onLoadingEntriesChanged: {
|
||||
if( loadingEntries && !displaying)
|
||||
displaying = true
|
||||
|
|
@ -60,10 +71,9 @@ Rectangle {
|
|||
interval: 5000
|
||||
repeat: false
|
||||
running: false
|
||||
onTriggered: container.proxyModel.chatRoomModel.resetMessageCount()
|
||||
onTriggered: if(container.proxyModel.chatRoomModel) container.proxyModel.chatRoomModel.resetMessageCount()
|
||||
}
|
||||
//property var sipAddressObserver: SipAddressesModel.getSipAddressObserver(proxyModel.fullPeerAddress, proxyModel.fullLocalAddress)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
|
|
@ -250,16 +260,20 @@ Rectangle {
|
|||
height: (item !== null && typeof(item)!== 'undefined')? item.height: 0
|
||||
Layout.fillWidth: true
|
||||
source: Logic.getComponentFromEntry($chatEntry)
|
||||
property int count: 0
|
||||
asynchronous: chat.count - count > 100
|
||||
onStatusChanged: if( status == Loader.Ready) ++chat.readyItems
|
||||
Component.onCompleted: count = ++chat.loaderCount
|
||||
Component.onDestruction: {
|
||||
--chat.loaderCount
|
||||
if( status == Loader.Ready)
|
||||
--chat.readyItems
|
||||
}
|
||||
property int loaderIndex: 0 // index of loader from remaining loaders
|
||||
property int remainingIndex : loaderIndex % ((chat.remainingLoadersCount) / chat.syncLoaderBatch) != 0 // Check loader index to remaining loader.
|
||||
onRemainingIndexChanged: if( remainingIndex == 0 && asynchronous) asynchronous = false
|
||||
asynchronous: true
|
||||
|
||||
onStatusChanged: if( status == Loader.Ready) {
|
||||
remainingIndex = -1 // overwrite to remove signal changed. That way, there is no more binding loops.
|
||||
--chat.remainingLoadersCount // Loader is ready: remove one from remaining count.
|
||||
}
|
||||
|
||||
Component.onCompleted: loaderIndex = ++chat.remainingLoadersCount // on new Loader : one more remaining
|
||||
Component.onDestruction: if( status != Loader.Ready) --chat.remainingLoadersCount // Remove remaining count if not loaded
|
||||
}
|
||||
|
||||
Connections{
|
||||
target: loader.item
|
||||
ignoreUnknownSignals: true
|
||||
|
|
@ -289,6 +303,10 @@ Rectangle {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
onGoToMessage:{
|
||||
container.goToMessage(message) // sometimes, there is no access to chat id (maybe because of cleaning component while loading new items). Use a global intermediate.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -489,3 +507,4 @@ Rectangle {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ Item {
|
|||
height: fitHeight
|
||||
onMainChatMessageModelChanged: if( mainChatMessageModel && mainChatMessageModel.replyChatMessageModel) chatMessageModel = mainChatMessageModel.replyChatMessageModel
|
||||
|
||||
signal goToMessage(ChatMessageModel message)
|
||||
|
||||
ColumnLayout{
|
||||
anchors.fill: parent
|
||||
|
|
@ -51,6 +52,10 @@ Item {
|
|||
iconSize: ChatReplyMessageStyle.header.replyIcon.iconSize
|
||||
height: iconSize
|
||||
overwriteColor: ChatReplyMessageStyle.header.color
|
||||
MouseArea{
|
||||
anchors.fill: parent
|
||||
onClicked: mainItem.goToMessage(mainItem.chatMessageModel)
|
||||
}
|
||||
}
|
||||
Text{
|
||||
id: headerText
|
||||
|
|
@ -62,6 +67,10 @@ Item {
|
|||
font.family: mainItem.customFont.family
|
||||
font.pointSize: Units.dp * (mainItem.customFont.pointSize + ChatReplyMessageStyle.header.pointSizeOffset)
|
||||
color: ChatReplyMessageStyle.header.color
|
||||
MouseArea{
|
||||
anchors.fill: parent
|
||||
onClicked: mainItem.goToMessage(mainItem.chatMessageModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle{
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ RowLayout {
|
|||
signal copySelectionDone()
|
||||
signal replyClicked()
|
||||
signal forwardClicked()
|
||||
signal goToMessage(ChatMessageModel message)
|
||||
|
||||
implicitHeight: message.height
|
||||
spacing: 0
|
||||
|
|
@ -67,6 +68,7 @@ RowLayout {
|
|||
onCopySelectionDone: parent.copySelectionDone()
|
||||
onReplyClicked: parent.replyClicked()
|
||||
onForwardClicked: parent.forwardClicked()
|
||||
onGoToMessage: parent.goToMessage(message)
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ Item {
|
|||
signal copySelectionDone()
|
||||
signal replyClicked()
|
||||
signal forwardClicked()
|
||||
signal goToMessage(ChatMessageModel message)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
property string lastTextSelected
|
||||
|
|
@ -87,6 +88,7 @@ Item {
|
|||
onFitWidthChanged:{
|
||||
rectangle.updateWidth()
|
||||
}
|
||||
onGoToMessage: container.goToMessage(message)
|
||||
}
|
||||
|
||||
ListView {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ Item {
|
|||
signal copySelectionDone()
|
||||
signal replyClicked()
|
||||
signal forwardClicked()
|
||||
signal goToMessage(ChatMessageModel message)
|
||||
|
||||
Message {
|
||||
id: message
|
||||
|
|
@ -27,6 +28,7 @@ Item {
|
|||
onCopySelectionDone: parent.copySelectionDone()
|
||||
onReplyClicked: parent.replyClicked()
|
||||
onForwardClicked: parent.forwardClicked()
|
||||
onGoToMessage: parent.goToMessage(message)
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 938bcf6d288eba2ff430165fe3da22f8cde9d42e
|
||||
Subproject commit 9cc416ad81725beb1d04baa9c6e1dc3d23db11ac
|
||||
Loading…
Add table
Reference in a new issue