forked from blue/squawk
message preview refactor, several bugs about label size, animations are now playing in previews
This commit is contained in:
parent
4307262f6e
commit
0d584c5aba
20 changed files with 498 additions and 164 deletions
|
@ -13,8 +13,6 @@ target_sources(squawk PRIVATE
|
|||
group.h
|
||||
item.cpp
|
||||
item.h
|
||||
messagefeed.cpp
|
||||
messagefeed.h
|
||||
participant.cpp
|
||||
participant.h
|
||||
presence.cpp
|
||||
|
@ -25,4 +23,4 @@ target_sources(squawk PRIVATE
|
|||
room.h
|
||||
roster.cpp
|
||||
roster.h
|
||||
)
|
||||
)
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
#define ELEMENT_H
|
||||
|
||||
#include "item.h"
|
||||
#include "messagefeed.h"
|
||||
|
||||
#include "ui/widgets/messageline/messagefeed.h"
|
||||
|
||||
namespace Models {
|
||||
|
||||
|
|
|
@ -1,657 +0,0 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "messagefeed.h"
|
||||
#include "element.h"
|
||||
#include "room.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
const QHash<int, QByteArray> Models::MessageFeed::roles = {
|
||||
{Text, "text"},
|
||||
{Sender, "sender"},
|
||||
{Date, "date"},
|
||||
{DeliveryState, "deliveryState"},
|
||||
{Correction, "correction"},
|
||||
{SentByMe,"sentByMe"},
|
||||
{Avatar, "avatar"},
|
||||
{Attach, "attach"},
|
||||
{Id, "id"},
|
||||
{Error, "error"},
|
||||
{Bulk, "bulk"}
|
||||
};
|
||||
|
||||
Models::MessageFeed::MessageFeed(const Element* ri, QObject* parent):
|
||||
QAbstractListModel(parent),
|
||||
storage(),
|
||||
indexById(storage.get<id>()),
|
||||
indexByTime(storage.get<time>()),
|
||||
rosterItem(ri),
|
||||
syncState(incomplete),
|
||||
uploads(),
|
||||
downloads(),
|
||||
failedDownloads(),
|
||||
failedUploads(),
|
||||
unreadMessages(new std::set<QString>()),
|
||||
observersAmount(0)
|
||||
{
|
||||
}
|
||||
|
||||
Models::MessageFeed::~MessageFeed()
|
||||
{
|
||||
delete unreadMessages;
|
||||
|
||||
for (Shared::Message* message : storage) {
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
|
||||
void Models::MessageFeed::addMessage(const Shared::Message& msg)
|
||||
{
|
||||
QString id = msg.getId();
|
||||
StorageById::const_iterator itr = indexById.find(id);
|
||||
if (itr != indexById.end()) {
|
||||
qDebug() << "received more then one message with the same id, skipping yet the new one";
|
||||
return;
|
||||
}
|
||||
|
||||
Shared::Message* copy = new Shared::Message(msg);
|
||||
StorageByTime::const_iterator tItr = indexByTime.upper_bound(msg.getTime());
|
||||
int position;
|
||||
if (tItr == indexByTime.end()) {
|
||||
position = storage.size();
|
||||
} else {
|
||||
position = indexByTime.rank(tItr);
|
||||
}
|
||||
beginInsertRows(QModelIndex(), position, position);
|
||||
storage.insert(copy);
|
||||
endInsertRows();
|
||||
|
||||
emit newMessage(msg);
|
||||
|
||||
if (observersAmount == 0 && !msg.getForwarded()) { //not to notify when the message is delivered by the carbon copy
|
||||
unreadMessages->insert(msg.getId()); //cuz it could be my own one or the one I read on another device
|
||||
emit unreadMessagesCountChanged();
|
||||
emit unnoticedMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::MessageFeed::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
StorageById::iterator itr = indexById.find(id);
|
||||
if (itr == indexById.end()) {
|
||||
qDebug() << "received a command to change a message, but the message couldn't be found, skipping";
|
||||
return;
|
||||
}
|
||||
|
||||
Shared::Message* msg = *itr;
|
||||
std::set<MessageRoles> changeRoles = detectChanges(*msg, data);
|
||||
QModelIndex index = modelIndexByTime(id, msg->getTime());
|
||||
Shared::Message::Change functor(data);
|
||||
bool success = indexById.modify(itr, functor);
|
||||
if (!success) {
|
||||
qDebug() << "received a command to change a message, but something went wrong modifying message in the feed, throwing error";
|
||||
throw 872;
|
||||
}
|
||||
|
||||
if (functor.hasIdBeenModified()) {
|
||||
changeRoles.insert(MessageRoles::Id);
|
||||
std::set<QString>::const_iterator umi = unreadMessages->find(id);
|
||||
if (umi != unreadMessages->end()) {
|
||||
unreadMessages->erase(umi);
|
||||
unreadMessages->insert(msg->getId());
|
||||
}
|
||||
}
|
||||
|
||||
if (changeRoles.size() > 0) {
|
||||
//change message is a final event in download/upload event train
|
||||
//only after changeMessage we can consider the download is done
|
||||
Progress::const_iterator dItr = downloads.find(id);
|
||||
bool attachOrError = changeRoles.count(MessageRoles::Attach) > 0 || changeRoles.count(MessageRoles::Error);
|
||||
if (dItr != downloads.end()) {
|
||||
if (attachOrError) {
|
||||
downloads.erase(dItr);
|
||||
} else if (changeRoles.count(MessageRoles::Id) > 0) {
|
||||
qreal progress = dItr->second;
|
||||
downloads.erase(dItr);
|
||||
downloads.insert(std::make_pair(msg->getId(), progress));
|
||||
}
|
||||
} else {
|
||||
dItr = uploads.find(id);
|
||||
if (dItr != uploads.end()) {
|
||||
if (attachOrError) {
|
||||
uploads.erase(dItr);
|
||||
} else if (changeRoles.count(MessageRoles::Id) > 0) {
|
||||
qreal progress = dItr->second;
|
||||
uploads.erase(dItr);
|
||||
uploads.insert(std::make_pair(msg->getId(), progress));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err::const_iterator eitr = failedDownloads.find(id);
|
||||
if (eitr != failedDownloads.end()) {
|
||||
failedDownloads.erase(eitr);
|
||||
changeRoles.insert(MessageRoles::Attach);
|
||||
} else {
|
||||
eitr = failedUploads.find(id);
|
||||
if (eitr != failedUploads.end()) {
|
||||
failedUploads.erase(eitr);
|
||||
changeRoles.insert(MessageRoles::Attach);
|
||||
}
|
||||
}
|
||||
|
||||
QVector<int> cr;
|
||||
for (MessageRoles role : changeRoles) {
|
||||
cr.push_back(role);
|
||||
}
|
||||
|
||||
emit dataChanged(index, index, cr);
|
||||
}
|
||||
}
|
||||
|
||||
std::set<Models::MessageFeed::MessageRoles> Models::MessageFeed::detectChanges(const Shared::Message& msg, const QMap<QString, QVariant>& data) const
|
||||
{
|
||||
std::set<MessageRoles> roles;
|
||||
Shared::Message::State state = msg.getState();
|
||||
QMap<QString, QVariant>::const_iterator itr = data.find("state");
|
||||
if (itr != data.end() && static_cast<Shared::Message::State>(itr.value().toUInt()) != state) {
|
||||
roles.insert(MessageRoles::DeliveryState);
|
||||
}
|
||||
|
||||
itr = data.find("outOfBandUrl");
|
||||
bool att = false;
|
||||
if (itr != data.end() && itr.value().toString() != msg.getOutOfBandUrl()) {
|
||||
roles.insert(MessageRoles::Attach);
|
||||
att = true;
|
||||
}
|
||||
|
||||
if (!att) {
|
||||
itr = data.find("attachPath");
|
||||
if (itr != data.end() && itr.value().toString() != msg.getAttachPath()) {
|
||||
roles.insert(MessageRoles::Attach);
|
||||
}
|
||||
}
|
||||
|
||||
if (state == Shared::Message::State::error) {
|
||||
itr = data.find("errorText");
|
||||
if (itr != data.end() && itr.value().toString() != msg.getErrorText()) {
|
||||
roles.insert(MessageRoles::Error);
|
||||
}
|
||||
}
|
||||
|
||||
itr = data.find("body");
|
||||
if (itr != data.end() && itr.value().toString() != msg.getBody()) {
|
||||
QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
|
||||
QDateTime correctionDate;
|
||||
if (dItr != data.end()) {
|
||||
correctionDate = dItr.value().toDateTime();
|
||||
} else {
|
||||
correctionDate = QDateTime::currentDateTimeUtc(); //in case there is no information about time of this correction it's applied
|
||||
}
|
||||
if (!msg.getEdited() || msg.getLastModified() < correctionDate) {
|
||||
roles.insert(MessageRoles::Text);
|
||||
roles.insert(MessageRoles::Correction);
|
||||
}
|
||||
} else {
|
||||
QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
|
||||
if (dItr != data.end()) {
|
||||
QDateTime ntime = dItr.value().toDateTime();
|
||||
if (msg.getTime() != ntime) {
|
||||
roles.insert(MessageRoles::Date);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
void Models::MessageFeed::removeMessage(const QString& id)
|
||||
{
|
||||
}
|
||||
|
||||
QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
int i = index.row();
|
||||
QVariant answer;
|
||||
|
||||
StorageByTime::const_iterator itr = indexByTime.nth(i);
|
||||
if (itr != indexByTime.end()) {
|
||||
const Shared::Message* msg = *itr;
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
case Text: {
|
||||
QString body = msg->getBody();
|
||||
if (body != msg->getOutOfBandUrl()) {
|
||||
answer = body;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Sender:
|
||||
if (sentByMe(*msg)) {
|
||||
answer = rosterItem->getAccountName();
|
||||
} else {
|
||||
if (rosterItem->isRoom()) {
|
||||
answer = msg->getFromResource();
|
||||
} else {
|
||||
answer = rosterItem->getDisplayedName();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Date:
|
||||
answer = msg->getTime();
|
||||
break;
|
||||
case DeliveryState:
|
||||
answer = static_cast<unsigned int>(msg->getState());
|
||||
break;
|
||||
case Correction:
|
||||
answer = msg->getEdited();
|
||||
break;
|
||||
case SentByMe:
|
||||
answer = sentByMe(*msg);
|
||||
break;
|
||||
case Avatar: {
|
||||
QString path;
|
||||
if (sentByMe(*msg)) {
|
||||
path = rosterItem->getAccountAvatarPath();
|
||||
} else if (!rosterItem->isRoom()) {
|
||||
if (rosterItem->getAvatarState() != Shared::Avatar::empty) {
|
||||
path = rosterItem->getAvatarPath();
|
||||
}
|
||||
} else {
|
||||
const Room* room = static_cast<const Room*>(rosterItem);
|
||||
path = room->getParticipantIconPath(msg->getFromResource());
|
||||
}
|
||||
|
||||
if (path.size() == 0) {
|
||||
answer = Shared::iconPath("user", true);
|
||||
} else {
|
||||
answer = path;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Attach:
|
||||
answer.setValue(fillAttach(*msg));
|
||||
break;
|
||||
case Id:
|
||||
answer.setValue(msg->getId());
|
||||
break;
|
||||
break;
|
||||
case Error:
|
||||
answer.setValue(msg->getErrorText());
|
||||
break;
|
||||
case Bulk: {
|
||||
FeedItem item;
|
||||
item.id = msg->getId();
|
||||
|
||||
std::set<QString>::const_iterator umi = unreadMessages->find(item.id);
|
||||
if (umi != unreadMessages->end()) {
|
||||
unreadMessages->erase(umi);
|
||||
emit unreadMessagesCount();
|
||||
}
|
||||
|
||||
item.sentByMe = sentByMe(*msg);
|
||||
item.date = msg->getTime();
|
||||
item.state = msg->getState();
|
||||
item.error = msg->getErrorText();
|
||||
item.correction = msg->getEdited();
|
||||
|
||||
QString body = msg->getBody();
|
||||
if (body != msg->getOutOfBandUrl()) {
|
||||
item.text = body;
|
||||
}
|
||||
|
||||
item.avatar.clear();
|
||||
if (item.sentByMe) {
|
||||
item.sender = rosterItem->getAccountName();
|
||||
item.avatar = rosterItem->getAccountAvatarPath();
|
||||
} else {
|
||||
if (rosterItem->isRoom()) {
|
||||
item.sender = msg->getFromResource();
|
||||
const Room* room = static_cast<const Room*>(rosterItem);
|
||||
item.avatar = room->getParticipantIconPath(msg->getFromResource());
|
||||
} else {
|
||||
item.sender = rosterItem->getDisplayedName();
|
||||
if (rosterItem->getAvatarState() != Shared::Avatar::empty) {
|
||||
item.avatar = rosterItem->getAvatarPath();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item.avatar.size() == 0) {
|
||||
item.avatar = Shared::iconPath("user", true);
|
||||
}
|
||||
item.attach = fillAttach(*msg);
|
||||
answer.setValue(item);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
int Models::MessageFeed::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
return storage.size();
|
||||
}
|
||||
|
||||
unsigned int Models::MessageFeed::unreadMessagesCount() const
|
||||
{
|
||||
return unreadMessages->size();
|
||||
}
|
||||
|
||||
bool Models::MessageFeed::canFetchMore(const QModelIndex& parent) const
|
||||
{
|
||||
return syncState == incomplete;
|
||||
}
|
||||
|
||||
void Models::MessageFeed::fetchMore(const QModelIndex& parent)
|
||||
{
|
||||
if (syncState == incomplete) {
|
||||
syncState = syncing;
|
||||
emit syncStateChange(syncState);
|
||||
emit requestStateChange(true);
|
||||
|
||||
if (storage.size() == 0) {
|
||||
emit requestArchive("");
|
||||
} else {
|
||||
emit requestArchive((*indexByTime.rbegin())->getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Models::MessageFeed::responseArchive(const std::list<Shared::Message> list, bool last)
|
||||
{
|
||||
Storage::size_type size = storage.size();
|
||||
|
||||
beginInsertRows(QModelIndex(), size, size + list.size() - 1);
|
||||
for (const Shared::Message& msg : list) {
|
||||
Shared::Message* copy = new Shared::Message(msg);
|
||||
storage.insert(copy);
|
||||
}
|
||||
endInsertRows();
|
||||
|
||||
if (syncState == syncing) {
|
||||
if (last) {
|
||||
syncState = complete;
|
||||
} else {
|
||||
syncState = incomplete;
|
||||
}
|
||||
emit syncStateChange(syncState);
|
||||
emit requestStateChange(false);
|
||||
}
|
||||
}
|
||||
|
||||
QModelIndex Models::MessageFeed::index(int row, int column, const QModelIndex& parent) const
|
||||
{
|
||||
if (!hasIndex(row, column, parent)) {
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
StorageByTime::iterator itr = indexByTime.nth(row);
|
||||
if (itr != indexByTime.end()) {
|
||||
Shared::Message* msg = *itr;
|
||||
|
||||
return createIndex(row, column, msg);
|
||||
} else {
|
||||
return QModelIndex();
|
||||
}
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> Models::MessageFeed::roleNames() const
|
||||
{
|
||||
return roles;
|
||||
}
|
||||
|
||||
bool Models::MessageFeed::sentByMe(const Shared::Message& msg) const
|
||||
{
|
||||
if (rosterItem->isRoom()) {
|
||||
const Room* room = static_cast<const Room*>(rosterItem);
|
||||
return room->getNick().toLower() == msg.getFromResource().toLower();
|
||||
} else {
|
||||
return msg.getOutgoing();
|
||||
}
|
||||
}
|
||||
|
||||
Models::Attachment Models::MessageFeed::fillAttach(const Shared::Message& msg) const
|
||||
{
|
||||
::Models::Attachment att;
|
||||
QString id = msg.getId();
|
||||
|
||||
att.localPath = msg.getAttachPath();
|
||||
att.remotePath = msg.getOutOfBandUrl();
|
||||
|
||||
if (att.remotePath.size() == 0) {
|
||||
if (att.localPath.size() == 0) {
|
||||
att.state = none;
|
||||
} else {
|
||||
Err::const_iterator eitr = failedUploads.find(id);
|
||||
if (eitr != failedUploads.end()) {
|
||||
att.state = errorUpload;
|
||||
att.error = eitr->second;
|
||||
} else {
|
||||
Progress::const_iterator itr = uploads.find(id);
|
||||
if (itr == uploads.end()) {
|
||||
att.state = local;
|
||||
} else {
|
||||
att.state = uploading;
|
||||
att.progress = itr->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (att.localPath.size() == 0) {
|
||||
Err::const_iterator eitr = failedDownloads.find(id);
|
||||
if (eitr != failedDownloads.end()) {
|
||||
att.state = errorDownload;
|
||||
att.error = eitr->second;
|
||||
} else {
|
||||
Progress::const_iterator itr = downloads.find(id);
|
||||
if (itr == downloads.end()) {
|
||||
att.state = remote;
|
||||
} else {
|
||||
att.state = downloading;
|
||||
att.progress = itr->second;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
att.state = ready;
|
||||
}
|
||||
}
|
||||
|
||||
return att;
|
||||
}
|
||||
|
||||
void Models::MessageFeed::downloadAttachment(const QString& messageId)
|
||||
{
|
||||
bool notify = false;
|
||||
Err::const_iterator eitr = failedDownloads.find(messageId);
|
||||
if (eitr != failedDownloads.end()) {
|
||||
failedDownloads.erase(eitr);
|
||||
notify = true;
|
||||
}
|
||||
|
||||
QModelIndex ind = modelIndexById(messageId);
|
||||
if (ind.isValid()) {
|
||||
std::pair<Progress::iterator, bool> progressPair = downloads.insert(std::make_pair(messageId, 0));
|
||||
if (progressPair.second) { //Only to take action if we weren't already downloading it
|
||||
Shared::Message* msg = static_cast<Shared::Message*>(ind.internalPointer());
|
||||
notify = true;
|
||||
emit fileDownloadRequest(msg->getOutOfBandUrl());
|
||||
} else {
|
||||
qDebug() << "Attachment download for message with id" << messageId << "is already in progress, skipping";
|
||||
}
|
||||
} else {
|
||||
qDebug() << "An attempt to download an attachment for the message that doesn't exist. ID:" << messageId;
|
||||
}
|
||||
|
||||
if (notify) {
|
||||
emit dataChanged(ind, ind, {MessageRoles::Attach});
|
||||
}
|
||||
}
|
||||
|
||||
bool Models::MessageFeed::registerUpload(const QString& messageId)
|
||||
{
|
||||
bool success = uploads.insert(std::make_pair(messageId, 0)).second;
|
||||
|
||||
QVector<int> roles({});
|
||||
Err::const_iterator eitr = failedUploads.find(messageId);
|
||||
if (eitr != failedUploads.end()) {
|
||||
failedUploads.erase(eitr);
|
||||
roles.push_back(MessageRoles::Attach);
|
||||
} else if (success) {
|
||||
roles.push_back(MessageRoles::Attach);
|
||||
}
|
||||
|
||||
QModelIndex ind = modelIndexById(messageId);
|
||||
emit dataChanged(ind, ind, roles);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void Models::MessageFeed::fileProgress(const QString& messageId, qreal value, bool up)
|
||||
{
|
||||
Progress* pr = 0;
|
||||
Err* err = 0;
|
||||
if (up) {
|
||||
pr = &uploads;
|
||||
err = &failedUploads;
|
||||
} else {
|
||||
pr = &downloads;
|
||||
err = &failedDownloads;
|
||||
}
|
||||
|
||||
QVector<int> roles({});
|
||||
Err::const_iterator eitr = err->find(messageId);
|
||||
if (eitr != err->end() && value != 1) { //like I want to clear this state when the download is started anew
|
||||
err->erase(eitr);
|
||||
roles.push_back(MessageRoles::Attach);
|
||||
}
|
||||
|
||||
Progress::iterator itr = pr->find(messageId);
|
||||
if (itr != pr->end()) {
|
||||
itr->second = value;
|
||||
QModelIndex ind = modelIndexById(messageId);
|
||||
emit dataChanged(ind, ind, roles);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::MessageFeed::fileComplete(const QString& messageId, bool up)
|
||||
{
|
||||
fileProgress(messageId, 1, up);
|
||||
}
|
||||
|
||||
void Models::MessageFeed::fileError(const QString& messageId, const QString& error, bool up)
|
||||
{
|
||||
Err* failed;
|
||||
Progress* loads;
|
||||
if (up) {
|
||||
failed = &failedUploads;
|
||||
loads = &uploads;
|
||||
} else {
|
||||
failed = &failedDownloads;
|
||||
loads = &downloads;
|
||||
}
|
||||
|
||||
Progress::iterator pitr = loads->find(messageId);
|
||||
if (pitr != loads->end()) {
|
||||
loads->erase(pitr);
|
||||
}
|
||||
|
||||
std::pair<Err::iterator, bool> pair = failed->insert(std::make_pair(messageId, error));
|
||||
if (!pair.second) {
|
||||
pair.first->second = error;
|
||||
}
|
||||
QModelIndex ind = modelIndexById(messageId);
|
||||
if (ind.isValid()) {
|
||||
emit dataChanged(ind, ind, {MessageRoles::Attach});
|
||||
}
|
||||
}
|
||||
|
||||
void Models::MessageFeed::incrementObservers()
|
||||
{
|
||||
++observersAmount;
|
||||
}
|
||||
|
||||
void Models::MessageFeed::decrementObservers()
|
||||
{
|
||||
--observersAmount;
|
||||
}
|
||||
|
||||
|
||||
QModelIndex Models::MessageFeed::modelIndexById(const QString& id) const
|
||||
{
|
||||
StorageById::const_iterator itr = indexById.find(id);
|
||||
if (itr != indexById.end()) {
|
||||
Shared::Message* msg = *itr;
|
||||
return modelIndexByTime(id, msg->getTime());
|
||||
}
|
||||
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
QModelIndex Models::MessageFeed::modelIndexByTime(const QString& id, const QDateTime& time) const
|
||||
{
|
||||
if (indexByTime.size() > 0) {
|
||||
StorageByTime::const_iterator tItr = indexByTime.lower_bound(time);
|
||||
StorageByTime::const_iterator tEnd = indexByTime.upper_bound(time);
|
||||
bool found = false;
|
||||
while (tItr != tEnd) {
|
||||
if (id == (*tItr)->getId()) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
++tItr;
|
||||
}
|
||||
|
||||
if (found) {
|
||||
int position = indexByTime.rank(tItr);
|
||||
return createIndex(position, 0, *tItr);
|
||||
}
|
||||
}
|
||||
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
void Models::MessageFeed::reportLocalPathInvalid(const QString& messageId)
|
||||
{
|
||||
StorageById::iterator itr = indexById.find(messageId);
|
||||
if (itr == indexById.end()) {
|
||||
qDebug() << "received a command to change a message, but the message couldn't be found, skipping";
|
||||
return;
|
||||
}
|
||||
|
||||
Shared::Message* msg = *itr;
|
||||
|
||||
emit localPathInvalid(msg->getAttachPath());
|
||||
|
||||
//gonna change the message in current model right away, to prevent spam on each attempt to draw element
|
||||
QModelIndex index = modelIndexByTime(messageId, msg->getTime());
|
||||
msg->setAttachPath("");
|
||||
|
||||
emit dataChanged(index, index, {MessageRoles::Attach});
|
||||
}
|
||||
|
||||
Models::MessageFeed::SyncState Models::MessageFeed::getSyncState() const
|
||||
{
|
||||
return syncState;
|
||||
}
|
|
@ -1,199 +0,0 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MESSAGEFEED_H
|
||||
#define MESSAGEFEED_H
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QDateTime>
|
||||
#include <QString>
|
||||
|
||||
#include <set>
|
||||
|
||||
#include <boost/multi_index_container.hpp>
|
||||
#include <boost/multi_index/ordered_index.hpp>
|
||||
#include <boost/multi_index/ranked_index.hpp>
|
||||
#include <boost/multi_index/mem_fun.hpp>
|
||||
|
||||
#include <shared/message.h>
|
||||
#include <shared/icons.h>
|
||||
|
||||
|
||||
namespace Models {
|
||||
class Element;
|
||||
struct Attachment;
|
||||
|
||||
class MessageFeed : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum SyncState {
|
||||
incomplete,
|
||||
syncing,
|
||||
complete
|
||||
};
|
||||
|
||||
MessageFeed(const Element* rosterItem, QObject *parent = nullptr);
|
||||
~MessageFeed();
|
||||
|
||||
void addMessage(const Shared::Message& msg);
|
||||
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
||||
void removeMessage(const QString& id);
|
||||
|
||||
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override;
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
|
||||
bool canFetchMore(const QModelIndex & parent) const override;
|
||||
void fetchMore(const QModelIndex & parent) override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
QModelIndex index(int row, int column, const QModelIndex & parent) const override;
|
||||
|
||||
void responseArchive(const std::list<Shared::Message> list, bool last);
|
||||
void downloadAttachment(const QString& messageId);
|
||||
bool registerUpload(const QString& messageId);
|
||||
void reportLocalPathInvalid(const QString& messageId);
|
||||
|
||||
unsigned int unreadMessagesCount() const;
|
||||
void fileProgress(const QString& messageId, qreal value, bool up);
|
||||
void fileError(const QString& messageId, const QString& error, bool up);
|
||||
void fileComplete(const QString& messageId, bool up);
|
||||
|
||||
void incrementObservers();
|
||||
void decrementObservers();
|
||||
SyncState getSyncState() const;
|
||||
|
||||
signals:
|
||||
void requestArchive(const QString& before);
|
||||
void requestStateChange(bool requesting);
|
||||
void fileDownloadRequest(const QString& url);
|
||||
void unreadMessagesCountChanged();
|
||||
void newMessage(const Shared::Message& msg);
|
||||
void unnoticedMessage(const Shared::Message& msg);
|
||||
void localPathInvalid(const QString& path);
|
||||
void syncStateChange(SyncState state);
|
||||
|
||||
public:
|
||||
enum MessageRoles {
|
||||
Text = Qt::UserRole + 1,
|
||||
Sender,
|
||||
Date,
|
||||
DeliveryState,
|
||||
Correction,
|
||||
SentByMe,
|
||||
Avatar,
|
||||
Attach,
|
||||
Id,
|
||||
Error,
|
||||
Bulk
|
||||
};
|
||||
|
||||
protected:
|
||||
bool sentByMe(const Shared::Message& msg) const;
|
||||
Attachment fillAttach(const Shared::Message& msg) const;
|
||||
QModelIndex modelIndexById(const QString& id) const;
|
||||
QModelIndex modelIndexByTime(const QString& id, const QDateTime& time) const;
|
||||
std::set<MessageRoles> detectChanges(const Shared::Message& msg, const QMap<QString, QVariant>& data) const;
|
||||
|
||||
private:
|
||||
//tags
|
||||
struct id {};
|
||||
struct time {};
|
||||
|
||||
typedef boost::multi_index_container<
|
||||
Shared::Message*,
|
||||
boost::multi_index::indexed_by<
|
||||
boost::multi_index::ordered_unique<
|
||||
boost::multi_index::tag<id>,
|
||||
boost::multi_index::const_mem_fun<
|
||||
Shared::Message,
|
||||
QString,
|
||||
&Shared::Message::getId
|
||||
>
|
||||
>,
|
||||
boost::multi_index::ranked_non_unique<
|
||||
boost::multi_index::tag<time>,
|
||||
boost::multi_index::const_mem_fun<
|
||||
Shared::Message,
|
||||
QDateTime,
|
||||
&Shared::Message::getTime
|
||||
>,
|
||||
std::greater<QDateTime>
|
||||
>
|
||||
>
|
||||
> Storage;
|
||||
|
||||
typedef Storage::index<id>::type StorageById;
|
||||
typedef Storage::index<time>::type StorageByTime;
|
||||
Storage storage;
|
||||
StorageById& indexById;
|
||||
StorageByTime& indexByTime;
|
||||
|
||||
const Element* rosterItem;
|
||||
SyncState syncState;
|
||||
|
||||
typedef std::map<QString, qreal> Progress;
|
||||
typedef std::map<QString, QString> Err;
|
||||
Progress uploads;
|
||||
Progress downloads;
|
||||
Err failedDownloads;
|
||||
Err failedUploads;
|
||||
|
||||
std::set<QString>* unreadMessages;
|
||||
uint16_t observersAmount;
|
||||
|
||||
|
||||
static const QHash<int, QByteArray> roles;
|
||||
};
|
||||
|
||||
enum AttachmentType {
|
||||
none,
|
||||
remote,
|
||||
local,
|
||||
downloading,
|
||||
uploading,
|
||||
errorDownload,
|
||||
errorUpload,
|
||||
ready
|
||||
};
|
||||
|
||||
struct Attachment {
|
||||
AttachmentType state;
|
||||
qreal progress;
|
||||
QString localPath;
|
||||
QString remotePath;
|
||||
QString error;
|
||||
};
|
||||
|
||||
struct FeedItem {
|
||||
QString id;
|
||||
QString text;
|
||||
QString sender;
|
||||
QString avatar;
|
||||
QString error;
|
||||
bool sentByMe;
|
||||
bool correction;
|
||||
QDateTime date;
|
||||
Shared::Message::State state;
|
||||
Attachment attach;
|
||||
};
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(Models::Attachment);
|
||||
Q_DECLARE_METATYPE(Models::FeedItem);
|
||||
|
||||
#endif // MESSAGEFEED_H
|
Loading…
Add table
Add a link
Reference in a new issue