File indexing completed on 2024-05-12 16:28:11

0001 // SPDX-FileCopyrightText: 2021 kaniini <https://git.pleroma.social/kaniini>
0002 // SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
0003 // SPDX-License-Identifier: GPL-3.0-only
0004 
0005 #include "timelinemodel.h"
0006 #include "account/abstractaccount.h"
0007 #include "accountmodel.h"
0008 #include "poll.h"
0009 #include <KLocalizedString>
0010 #include <QJsonArray>
0011 #include <QJsonObject>
0012 #include <QtMath>
0013 #include <algorithm>
0014 
0015 TimelineModel::TimelineModel(QObject *parent)
0016     : AbstractTimelineModel(parent)
0017     , m_manager(&AccountManager::instance())
0018 {
0019 }
0020 
0021 void TimelineModel::init()
0022 {
0023     m_manager = &AccountManager::instance();
0024     m_account = m_manager->selectedAccount();
0025 
0026     if (m_account) {
0027         connect(m_account, &AbstractAccount::streamingEvent, this, &TimelineModel::handleEvent);
0028     }
0029 
0030     connect(m_manager, &AccountManager::invalidated, this, [=](AbstractAccount *account) {
0031         if (m_account == account) {
0032             qDebug() << "Invalidating account" << account;
0033 
0034             beginResetModel();
0035             m_timeline.clear();
0036             endResetModel();
0037 
0038             fillTimeline();
0039         }
0040     });
0041 
0042     connect(m_manager, &AccountManager::accountSelected, this, [=](AbstractAccount *account) {
0043         if (m_account == account) {
0044             return;
0045         }
0046 
0047         if (m_account) {
0048             disconnect(m_account, &AbstractAccount::streamingEvent, this, &TimelineModel::handleEvent);
0049         }
0050 
0051         m_account = account;
0052 
0053         connect(m_account, &AbstractAccount::streamingEvent, this, &TimelineModel::handleEvent);
0054 
0055         beginResetModel();
0056         qDeleteAll(m_timeline);
0057         m_timeline.clear();
0058         endResetModel();
0059 
0060         setLoading(false);
0061 
0062         Q_EMIT nameChanged();
0063         fillTimeline();
0064     });
0065 
0066     fillTimeline();
0067 }
0068 
0069 void TimelineModel::fetchedTimeline(const QByteArray &data, bool alwaysAppendToEnd)
0070 {
0071     QList<Post *> posts;
0072 
0073     const auto doc = QJsonDocument::fromJson(data);
0074 
0075     if (!doc.isArray()) {
0076         setLoading(false);
0077         return;
0078     }
0079 
0080     const auto array = doc.array();
0081 
0082     if (array.isEmpty()) {
0083         setLoading(false);
0084         return;
0085     }
0086 
0087     std::transform(array.cbegin(), array.cend(), std::back_inserter(posts), [this](const QJsonValue &value) -> Post * {
0088         auto post = new Post(m_account, value.toObject(), this);
0089         if (!post->hidden()) {
0090             return post;
0091         } else {
0092             return nullptr;
0093         }
0094     });
0095 
0096     posts.erase(std::remove_if(posts.begin(),
0097                                posts.end(),
0098                                [](Post *post) {
0099                                    return post == nullptr;
0100                                }),
0101                 posts.end());
0102 
0103     if (!m_timeline.isEmpty()) {
0104         if (alwaysAppendToEnd) {
0105             beginInsertRows({}, m_timeline.size(), m_timeline.size() + posts.size() - 1);
0106             m_timeline += posts;
0107             endInsertRows();
0108         } else {
0109             const auto postOld = m_timeline.first();
0110             const auto postNew = posts.first();
0111             if (postOld->originalPostId() > postNew->originalPostId()) {
0112                 const int row = m_timeline.size();
0113                 const int last = row + posts.size() - 1;
0114                 beginInsertRows({}, row, last);
0115                 m_timeline += posts;
0116                 endInsertRows();
0117             } else {
0118                 beginInsertRows({}, 0, posts.size() - 1);
0119                 m_timeline = posts + m_timeline;
0120                 endInsertRows();
0121             }
0122         }
0123     } else {
0124         beginInsertRows({}, 0, posts.size() - 1);
0125         m_timeline = posts;
0126         endInsertRows();
0127     }
0128     setLoading(false);
0129 }
0130 
0131 void TimelineModel::fetchMore(const QModelIndex &parent)
0132 {
0133     Q_UNUSED(parent);
0134 
0135     if (m_timeline.empty()) {
0136         return;
0137     }
0138 
0139     const auto p = m_timeline.last();
0140 
0141     if (m_shouldLoadMore) {
0142         fillTimeline(p->originalPostId());
0143     } else {
0144         m_shouldLoadMore = true;
0145     }
0146 }
0147 
0148 void TimelineModel::setShouldLoadMore(bool shouldLoadMore)
0149 {
0150     m_shouldLoadMore = shouldLoadMore;
0151 }
0152 
0153 bool TimelineModel::canFetchMore(const QModelIndex &parent) const
0154 {
0155     Q_UNUSED(parent);
0156     return true;
0157 }
0158 
0159 int TimelineModel::rowCount(const QModelIndex &parent) const
0160 {
0161     Q_UNUSED(parent)
0162     return m_timeline.size();
0163 }
0164 
0165 QVariant TimelineModel::data(const QModelIndex &index, int role) const
0166 {
0167     if (!index.isValid()) {
0168         return {};
0169     }
0170     if (role == TypeRole) {
0171         return false;
0172     }
0173     return postData(m_timeline[index.row()], role);
0174 }
0175 
0176 void TimelineModel::actionReply(const QModelIndex &index)
0177 {
0178     int row = index.row();
0179     auto p = m_timeline[row];
0180 
0181     Q_EMIT wantReply(m_account, p, index);
0182 }
0183 
0184 void TimelineModel::actionFavorite(const QModelIndex &index)
0185 {
0186     const int row = index.row();
0187     const auto post = m_timeline[row];
0188     AbstractTimelineModel::actionFavorite(index, post);
0189 }
0190 
0191 void TimelineModel::actionRepeat(const QModelIndex &index)
0192 {
0193     const int row = index.row();
0194     const auto post = m_timeline[row];
0195     AbstractTimelineModel::actionRepeat(index, post);
0196 }
0197 
0198 void TimelineModel::actionVote(const QModelIndex &index, const QList<int> &choices)
0199 {
0200     const int row = index.row();
0201     const auto post = m_timeline[row];
0202     const auto poll = post->poll();
0203     Q_ASSERT(poll);
0204 
0205     QJsonObject obj;
0206     QJsonArray array;
0207     std::transform(
0208         choices.cbegin(),
0209         choices.cend(),
0210         std::back_inserter(array),
0211         [](int choice) -> auto{ return choice; });
0212     obj["choices"] = array;
0213     QJsonDocument doc(obj);
0214     const auto id = poll->id();
0215 
0216     m_account->post(m_account->apiUrl(QString("/api/v1/polls/%1/votes").arg(id)), doc, true, this, [this, id](QNetworkReply *reply) {
0217         int i = 0;
0218         for (auto &post : m_timeline) {
0219             if (post->poll() && post->poll()->id() == id) {
0220                 const auto newPoll = QJsonDocument::fromJson(reply->readAll()).object();
0221                 post->setPollJson(newPoll);
0222                 Q_EMIT dataChanged(this->index(i, 0), this->index(i, 0), {PollRole});
0223                 break;
0224             }
0225             i++;
0226         }
0227     });
0228 }
0229 
0230 void TimelineModel::actionBookmark(const QModelIndex &index)
0231 {
0232     int row = index.row();
0233     const auto post = m_timeline[row];
0234 
0235     AbstractTimelineModel::actionBookmark(index, post);
0236 }
0237 
0238 void TimelineModel::actionPin(const QModelIndex &index)
0239 {
0240     int row = index.row();
0241     const auto post = m_timeline[row];
0242 
0243     AbstractTimelineModel::actionPin(index, post);
0244 }
0245 
0246 void TimelineModel::actionRedraft(const QModelIndex &index, bool isEdit)
0247 {
0248     int row = index.row();
0249     auto p = m_timeline[row];
0250 
0251     AbstractTimelineModel::actionRedraft(index, p, isEdit);
0252 }
0253 
0254 void TimelineModel::actionDelete(const QModelIndex &index)
0255 {
0256     int row = index.row();
0257     auto p = m_timeline[row];
0258 
0259     AbstractTimelineModel::actionDelete(index, p);
0260 
0261     beginRemoveRows({}, row, row);
0262     m_timeline.removeAt(row);
0263     endRemoveRows();
0264 }
0265 
0266 void TimelineModel::handleEvent(AbstractAccount::StreamingEventType eventType, const QByteArray &payload)
0267 {
0268     if (eventType == AbstractAccount::StreamingEventType::DeleteEvent) {
0269         int i = 0;
0270         for (const auto &post : std::as_const(m_timeline)) {
0271             if (post->originalPostId().toUtf8() == payload) {
0272                 beginRemoveRows({}, i, i);
0273                 m_timeline.removeAt(i);
0274                 endRemoveRows();
0275                 break;
0276             }
0277             i++;
0278         }
0279     }
0280 }