File indexing completed on 2024-05-12 05:04:15

0001 // SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
0002 // SPDX-License-Identifier: GPL-3.0-only
0003 
0004 #include "accountmodel.h"
0005 
0006 #include "relationship.h"
0007 
0008 #include <KLocalizedString>
0009 
0010 AccountModel::AccountModel(QObject *parent)
0011     : TimelineModel(parent)
0012 {
0013     init();
0014 
0015     connect(this, &AccountModel::identityChanged, this, &TimelineModel::nameChanged);
0016     connect(this, &AccountModel::filtersChanged, this, [this] {
0017         reset();
0018         fillTimeline();
0019     });
0020     connect(this, &AccountModel::tabChanged, this, &AccountModel::updateTabFilters);
0021 }
0022 
0023 bool AccountModel::isSelf() const
0024 {
0025     if (m_identity == nullptr) {
0026         return false;
0027     }
0028 
0029     return m_account->identity()->id() == m_identity->id();
0030 }
0031 
0032 QString AccountModel::displayName() const
0033 {
0034     if (!m_identity) {
0035         return i18n("Loading");
0036     }
0037 
0038     return m_identity->displayNameHtml();
0039 }
0040 
0041 void AccountModel::fillTimeline(const QString &fromId)
0042 {
0043     if (m_accountId.isEmpty() || m_accountId.isNull()) {
0044         return;
0045     }
0046     setLoading(true);
0047 
0048     // Fetch pinned posts if we are starting from the top
0049     const auto fetchPinned = fromId.isNull() && !m_excludePinned;
0050     auto uriStatus = m_account->apiUrl(QStringLiteral("/api/v1/accounts/%1/statuses").arg(m_accountId));
0051 
0052     auto statusQuery = QUrlQuery();
0053     if (m_excludeReplies) {
0054         statusQuery.addQueryItem(QStringLiteral("exclude_replies"), QStringLiteral("true"));
0055     }
0056     if (m_excludeBoosts) {
0057         statusQuery.addQueryItem(QStringLiteral("exclude_reblogs"), QStringLiteral("true"));
0058     }
0059     if (m_onlyMedia) {
0060         statusQuery.addQueryItem(QStringLiteral("only_media"), QStringLiteral("true"));
0061     }
0062     if (!m_tagged.isEmpty()) {
0063         statusQuery.addQueryItem(QStringLiteral("tagged"), m_tagged);
0064     }
0065     if (!fromId.isNull()) {
0066         statusQuery.addQueryItem(QStringLiteral("max_id"), fromId);
0067     }
0068     if (!statusQuery.isEmpty()) {
0069         uriStatus.setQuery(statusQuery);
0070     }
0071 
0072     auto uriPinned = m_account->apiUrl(QStringLiteral("/api/v1/accounts/%1/statuses").arg(m_accountId));
0073     uriPinned.setQuery(QUrlQuery{{
0074         QStringLiteral("pinned"),
0075         QStringLiteral("true"),
0076     }});
0077 
0078     const auto account = m_account;
0079     const auto id = m_accountId;
0080 
0081     auto handleError = [this](QNetworkReply *reply) {
0082         Q_UNUSED(reply);
0083         setLoading(false);
0084     };
0085 
0086     auto onFetchPinned = [this, id, account](QNetworkReply *reply) {
0087         if (m_account != account || m_accountId != id) {
0088             setLoading(false);
0089             return;
0090         }
0091         const auto data = reply->readAll();
0092         const auto doc = QJsonDocument::fromJson(data);
0093         if (!doc.isArray()) {
0094             setLoading(false);
0095             return;
0096         }
0097         const auto array = doc.array();
0098         if (array.isEmpty()) {
0099             setLoading(false);
0100             return;
0101         }
0102 
0103         QList<Post *> posts;
0104         std::transform(array.cbegin(), array.cend(), std::back_inserter(posts), [this](const QJsonValue &value) {
0105             auto post = new Post(m_account, value.toObject(), this);
0106             post->setPinned(true);
0107             return post;
0108         });
0109         std::reverse(posts.begin(), posts.end());
0110         beginInsertRows({}, 0, posts.size() - 1);
0111         m_timeline = posts + m_timeline;
0112         endInsertRows();
0113         setLoading(false);
0114     };
0115 
0116     auto onFetchAccount = [account, id, fetchPinned, uriPinned, handleError, onFetchPinned, fromId, this](QNetworkReply *reply) {
0117         if (m_account != account || m_accountId != id) {
0118             setLoading(false);
0119             return;
0120         }
0121 
0122         // if we just restarted the fetch (fromId is null) then we must clear the previous array
0123         // this can happen if we just entered the profile page (okay, just a no-op) or if the filters change
0124         if (fromId.isNull()) {
0125             reset();
0126         }
0127 
0128         fetchedTimeline(reply->readAll(), true);
0129         if (fetchPinned) {
0130             m_account->get(uriPinned, true, this, onFetchPinned, handleError);
0131         } else {
0132             setLoading(false);
0133         }
0134     };
0135 
0136     m_account->get(uriStatus, true, this, onFetchAccount, handleError);
0137 }
0138 
0139 Identity *AccountModel::identity() const
0140 {
0141     return m_identity.get();
0142 }
0143 
0144 QString AccountModel::accountId() const
0145 {
0146     return m_accountId;
0147 }
0148 
0149 void AccountModel::setAccountId(const QString &accountId)
0150 {
0151     if (accountId == m_accountId || accountId.isEmpty()) {
0152         return;
0153     }
0154     m_accountId = accountId;
0155     Q_EMIT accountIdChanged();
0156 
0157     if (!m_account->identityCached(accountId)) {
0158         QUrl uriAccount(m_account->instanceUri());
0159         uriAccount.setPath(QStringLiteral("/api/v1/accounts/%1").arg(accountId));
0160 
0161         m_account->get(uriAccount, true, this, [this, accountId](QNetworkReply *reply) {
0162             const auto data = reply->readAll();
0163             const auto doc = QJsonDocument::fromJson(data);
0164 
0165             m_identity = m_account->identityLookup(accountId, doc.object());
0166             Q_EMIT identityChanged();
0167             updateRelationships();
0168         });
0169     } else {
0170         m_identity = m_account->identityLookup(accountId, {});
0171         Q_EMIT identityChanged();
0172         updateRelationships();
0173     }
0174     Q_EMIT accountIdChanged();
0175 
0176     fillTimeline();
0177 }
0178 
0179 AbstractAccount *AccountModel::account() const
0180 {
0181     return m_account;
0182 }
0183 
0184 void AccountModel::updateRelationships()
0185 {
0186     if (m_account->identity()->id() == m_identity->id()) {
0187         return;
0188     }
0189 
0190     // Fetch relationship. Don't cache this; it's lightweight.
0191     QUrl uriRelationship(m_account->instanceUri());
0192     uriRelationship.setPath(QStringLiteral("/api/v1/accounts/relationships"));
0193     uriRelationship.setQuery(QUrlQuery{
0194         {QStringLiteral("id[]"), m_identity->id()},
0195     });
0196 
0197     m_account->get(uriRelationship, true, this, [this](QNetworkReply *reply) {
0198         const auto doc = QJsonDocument::fromJson(reply->readAll());
0199         if (!doc.isArray()) {
0200             qWarning() << "Data returned from Relationship network request is not an array"
0201                        << "data: " << doc;
0202             return;
0203         }
0204 
0205         // We only are requesting for a single relationship, so doc should only contain one element
0206         m_identity->setRelationship(new Relationship(m_identity.get(), doc[0].toObject()));
0207         Q_EMIT identityChanged();
0208     });
0209 }
0210 
0211 void AccountModel::updateTabFilters()
0212 {
0213     switch (m_currentTab) {
0214     case Posts: 
0215         m_excludeBoosts = false;
0216         m_excludePinned = false;
0217         m_excludeReplies = true;
0218         m_onlyMedia = false;
0219         break;
0220     case Replies:
0221         m_excludeBoosts = true;
0222         m_excludePinned = true;
0223         m_excludeReplies = false;
0224         m_onlyMedia = false;
0225         break;
0226     case Media:
0227         m_excludeBoosts = true;
0228         m_excludePinned = true;
0229         m_excludeReplies = false;
0230         m_onlyMedia = true;
0231         break;
0232     default:
0233         break;
0234     }
0235     Q_EMIT filtersChanged();
0236 }
0237 
0238 void AccountModel::reset()
0239 {
0240     beginResetModel();
0241     qDeleteAll(m_timeline);
0242     m_timeline.clear();
0243     endResetModel();
0244 }
0245 
0246 #include "moc_accountmodel.cpp"