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 &notification : 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 }