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"