File indexing completed on 2024-05-12 05:04:14
0001 // SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org> 0002 // SPDX-License-Identifier: GPL-3.0-or-later 0003 0004 #include "notificationmodel.h" 0005 0006 #include "abstractaccount.h" 0007 0008 #include <KLocalizedString> 0009 0010 NotificationModel::NotificationModel(QObject *parent) 0011 : AbstractTimelineModel(parent) 0012 { 0013 m_manager = &AccountManager::instance(); 0014 m_account = m_manager->selectedAccount(); 0015 0016 connect(m_manager, &AccountManager::invalidated, this, [=](AbstractAccount *account) { 0017 if (m_account == account) { 0018 qDebug() << "Invalidating account" << account; 0019 0020 beginResetModel(); 0021 m_notifications.clear(); 0022 endResetModel(); 0023 m_next = QUrl(); 0024 setLoading(false); 0025 } 0026 }); 0027 0028 connect(m_manager, &AccountManager::accountSelected, this, [=](AbstractAccount *account) { 0029 if (m_account != account) { 0030 m_account = account; 0031 0032 beginResetModel(); 0033 m_notifications.clear(); 0034 endResetModel(); 0035 0036 fillTimeline(); 0037 } 0038 }); 0039 0040 connect(this, &NotificationModel::excludeTypesChanged, this, [this] { 0041 beginResetModel(); 0042 m_notifications.clear(); 0043 endResetModel(); 0044 m_next = QUrl(); 0045 setLoading(false); 0046 fillTimeline(); 0047 }); 0048 0049 setLoading(false); 0050 fillTimeline(); 0051 } 0052 0053 QStringList NotificationModel::excludeTypes() const 0054 { 0055 return m_excludeTypes; 0056 } 0057 0058 void NotificationModel::setExcludesTypes(const QStringList &excludeTypes) 0059 { 0060 if (m_excludeTypes == excludeTypes) { 0061 return; 0062 } 0063 0064 m_excludeTypes = excludeTypes; 0065 Q_EMIT excludeTypesChanged(); 0066 } 0067 0068 void NotificationModel::fillTimeline(const QUrl &next) 0069 { 0070 if (!m_account) { 0071 return; 0072 } 0073 0074 if (m_loading) { 0075 return; 0076 } 0077 setLoading(true); 0078 QUrl uri; 0079 if (next.isEmpty()) { 0080 uri = QUrl::fromUserInput(m_account->instanceUri()); 0081 uri.setPath(QStringLiteral("/api/v1/notifications")); 0082 } else { 0083 uri = next; 0084 } 0085 QUrlQuery urlQuery(uri); 0086 for (const auto &excludeType : std::as_const(m_excludeTypes)) { 0087 urlQuery.addQueryItem(QStringLiteral("exclude_types[]"), excludeType); 0088 } 0089 uri.setQuery(urlQuery); 0090 0091 m_account->get(uri, true, this, [=](QNetworkReply *reply) { 0092 const auto data = reply->readAll(); 0093 const auto doc = QJsonDocument::fromJson(data); 0094 0095 if (!doc.isArray()) { 0096 m_account->errorOccured(i18n("Error occurred when fetching the latest notification.")); 0097 return; 0098 } 0099 static QRegularExpression re(QStringLiteral("<(.*)>; rel=\"next\"")); 0100 const auto next = reply->rawHeader(QByteArrayLiteral("Link")); 0101 const auto match = re.match(QString::fromUtf8(next)); 0102 m_next = QUrl::fromUserInput(match.captured(1)); 0103 0104 QList<std::shared_ptr<Notification>> notifications; 0105 const auto values = doc.array(); 0106 for (const auto &value : values) { 0107 const QJsonObject obj = value.toObject(); 0108 const auto notification = std::make_shared<Notification>(m_account, obj, this); 0109 0110 notifications.push_back(notification); 0111 } 0112 0113 if (notifications.isEmpty()) { 0114 setLoading(false); 0115 return; 0116 } 0117 0118 beginInsertRows({}, m_notifications.count(), m_notifications.count() + notifications.count() - 1); 0119 m_notifications.append(notifications); 0120 endInsertRows(); 0121 0122 setLoading(false); 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 } 0258 0259 #include "moc_notificationmodel.cpp"