File indexing completed on 2024-05-12 16:28:10
0001 // SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org> 0002 // SPDX-License-Identifier: GPL-3.0-or-later 0003 0004 #include "notificationmodel.h" 0005 #include "account/abstractaccount.h" 0006 #include "abstracttimelinemodel.h" 0007 #include <KLocalizedString> 0008 #include <QUrlQuery> 0009 #include <QtMath> 0010 0011 NotificationModel::NotificationModel(QObject *parent) 0012 : AbstractTimelineModel(parent) 0013 { 0014 m_manager = &AccountManager::instance(); 0015 m_account = m_manager->selectedAccount(); 0016 0017 connect(m_manager, &AccountManager::invalidated, this, [=](AbstractAccount *account) { 0018 if (m_account == account) { 0019 qDebug() << "Invalidating account" << account; 0020 0021 beginResetModel(); 0022 m_notifications.clear(); 0023 endResetModel(); 0024 m_next = QString(); 0025 setLoading(false); 0026 } 0027 }); 0028 0029 connect(m_manager, &AccountManager::accountSelected, this, [=](AbstractAccount *account) { 0030 if (m_account != account) { 0031 m_account = account; 0032 0033 beginResetModel(); 0034 m_notifications.clear(); 0035 endResetModel(); 0036 0037 fillTimeline(); 0038 } 0039 }); 0040 0041 connect(this, &NotificationModel::excludeTypesChanged, this, [this] { 0042 beginResetModel(); 0043 m_notifications.clear(); 0044 endResetModel(); 0045 m_next = QString(); 0046 setLoading(false); 0047 fillTimeline(); 0048 }); 0049 0050 setLoading(false); 0051 fillTimeline(); 0052 } 0053 0054 QStringList NotificationModel::excludeTypes() const 0055 { 0056 return m_excludeTypes; 0057 } 0058 0059 void NotificationModel::setExcludesTypes(const QStringList &excludeTypes) 0060 { 0061 if (m_excludeTypes == excludeTypes) { 0062 return; 0063 } 0064 0065 m_excludeTypes = excludeTypes; 0066 Q_EMIT excludeTypesChanged(); 0067 } 0068 0069 void NotificationModel::fillTimeline(const QUrl &next) 0070 { 0071 if (!m_account) { 0072 return; 0073 } 0074 0075 if (m_loading) { 0076 return; 0077 } 0078 setLoading(true); 0079 QUrl uri; 0080 if (next.isEmpty()) { 0081 uri = QUrl::fromUserInput(m_account->instanceUri()); 0082 uri.setPath(QStringLiteral("/api/v1/notifications")); 0083 } else { 0084 uri = next; 0085 } 0086 QUrlQuery urlQuery(uri); 0087 for (const auto &excludeType : std::as_const(m_excludeTypes)) { 0088 urlQuery.addQueryItem("exclude_types[]", excludeType); 0089 } 0090 uri.setQuery(urlQuery); 0091 0092 m_account->get(uri, true, this, [=](QNetworkReply *reply) { 0093 const auto data = reply->readAll(); 0094 const auto doc = QJsonDocument::fromJson(data); 0095 0096 setLoading(false); 0097 0098 if (!doc.isArray()) { 0099 m_account->errorOccured(i18n("Error occurred when fetching the latest notification.")); 0100 return; 0101 } 0102 static QRegularExpression re("<(.*)>; rel=\"next\""); 0103 const auto next = reply->rawHeader(QByteArrayLiteral("Link")); 0104 const auto match = re.match(next); 0105 m_next = QUrl::fromUserInput(match.captured(1)); 0106 0107 QList<std::shared_ptr<Notification>> notifications; 0108 const auto values = doc.array(); 0109 for (const auto &value : values) { 0110 const QJsonObject obj = value.toObject(); 0111 const auto notification = std::make_shared<Notification>(m_account, obj, this); 0112 0113 notifications.push_back(notification); 0114 } 0115 0116 if (notifications.isEmpty()) { 0117 return; 0118 } 0119 0120 beginInsertRows({}, m_notifications.count(), m_notifications.count() + notifications.count() - 1); 0121 m_notifications.append(notifications); 0122 endInsertRows(); 0123 }); 0124 } 0125 0126 void NotificationModel::fetchMore(const QModelIndex &parent) 0127 { 0128 Q_UNUSED(parent); 0129 0130 if (m_notifications.isEmpty() || !m_next.isValid() || m_loading) { 0131 return; 0132 } 0133 0134 fillTimeline(m_next); 0135 } 0136 0137 bool NotificationModel::canFetchMore(const QModelIndex &parent) const 0138 { 0139 Q_UNUSED(parent); 0140 0141 // Todo detect when there is nothing left 0142 return !loading(); 0143 } 0144 0145 int NotificationModel::rowCount(const QModelIndex &parent) const 0146 { 0147 Q_UNUSED(parent) 0148 0149 return m_notifications.size(); 0150 } 0151 0152 QVariant NotificationModel::data(const QModelIndex &index, int role) const 0153 { 0154 if (!index.isValid()) { 0155 return {}; 0156 } 0157 int row = index.row(); 0158 auto notification = m_notifications[row]; 0159 auto post = notification->post(); 0160 0161 switch (role) { 0162 case TypeRole: 0163 return notification->type(); 0164 case IsBoostedRole: 0165 return post != nullptr ? (post->boosted() || notification->type() == Notification::Repeat) : false; 0166 case BoostAuthorIdentityRole: { 0167 if (post != nullptr) { 0168 if (post->boostIdentity()) { 0169 return QVariant::fromValue<Identity *>(post->boostIdentity().get()); 0170 } 0171 if (notification->type() == Notification::Repeat) { 0172 return QVariant::fromValue<Identity *>(notification->identity().get()); 0173 } 0174 } 0175 return {}; 0176 } 0177 case NotificationActorIdentityRole: 0178 return QVariant::fromValue(notification->identity().get()); 0179 case AuthorIdentityRole: 0180 if (notification->type() == Notification::Follow || notification->type() == Notification::FollowRequest) { 0181 return QVariant::fromValue<Identity *>(notification->identity().get()); 0182 } else if (post != nullptr) { 0183 return QVariant::fromValue<Identity *>(post->authorIdentity().get()); 0184 } 0185 default: 0186 if (post != nullptr) { 0187 return postData(post, role); 0188 } 0189 } 0190 0191 return {}; 0192 } 0193 0194 void NotificationModel::actionReply(const QModelIndex &index) 0195 { 0196 int row = index.row(); 0197 auto p = m_notifications[row]->post(); 0198 if (p != nullptr) { 0199 Q_EMIT wantReply(m_account, p, index); 0200 } 0201 } 0202 0203 void NotificationModel::actionFavorite(const QModelIndex &index) 0204 { 0205 int row = index.row(); 0206 auto post = m_notifications[row]->post(); 0207 if (post != nullptr) { 0208 AbstractTimelineModel::actionFavorite(index, post); 0209 } 0210 } 0211 0212 void NotificationModel::actionRepeat(const QModelIndex &index) 0213 { 0214 const int row = index.row(); 0215 const auto post = m_notifications[row]->post(); 0216 if (post != nullptr) { 0217 AbstractTimelineModel::actionRepeat(index, post); 0218 } 0219 } 0220 0221 void NotificationModel::actionRedraft(const QModelIndex &index, bool isEdit) 0222 { 0223 const int row = index.row(); 0224 const auto p = m_notifications[row]->post(); 0225 if (p != nullptr) { 0226 AbstractTimelineModel::actionRedraft(index, p, isEdit); 0227 } 0228 } 0229 0230 void NotificationModel::actionBookmark(const QModelIndex &index) 0231 { 0232 const int row = index.row(); 0233 const auto post = m_notifications[row]->post(); 0234 if (post != nullptr) { 0235 AbstractTimelineModel::actionBookmark(index, post); 0236 } 0237 } 0238 0239 void NotificationModel::actionDelete(const QModelIndex &index) 0240 { 0241 const auto p = m_notifications[index.row()]->post(); 0242 if (p == nullptr) { 0243 return; 0244 } 0245 0246 AbstractTimelineModel::actionDelete(index, p); 0247 0248 // TODO: this sucks 0249 for (auto ¬ification : m_notifications) { 0250 if (notification->post() != nullptr && notification->post()->postId() == p->postId()) { 0251 int row = m_notifications.indexOf(notification); 0252 beginRemoveRows({}, row, row); 0253 m_notifications.removeOne(notification); 0254 endRemoveRows(); 0255 } 0256 } 0257 }