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 }