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 &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 }
0258 
0259 #include "moc_notificationmodel.cpp"