File indexing completed on 2024-05-12 16:28:09

0001 // SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
0002 // SPDX-License-Identifier: GPL-3.0-or-later
0003 
0004 #include "abstracttimelinemodel.h"
0005 #include "account/abstractaccount.h"
0006 #include "account/identity.h"
0007 #include "editor/attachmenteditormodel.h"
0008 #include "editor/posteditorbackend.h"
0009 #include "poll.h"
0010 #include "post.h"
0011 #include <KLocalizedString>
0012 #include <QtMath>
0013 #include <qvariant.h>
0014 
0015 AbstractTimelineModel::AbstractTimelineModel(QObject *parent)
0016     : QAbstractListModel(parent)
0017 {
0018 }
0019 
0020 bool AbstractTimelineModel::loading() const
0021 {
0022     return m_loading;
0023 }
0024 
0025 void AbstractTimelineModel::setLoading(bool loading)
0026 {
0027     if (m_loading == loading) {
0028         return;
0029     }
0030     m_loading = loading;
0031     Q_EMIT loadingChanged();
0032 }
0033 
0034 QHash<int, QByteArray> AbstractTimelineModel::roleNames() const
0035 {
0036     return {
0037         {IdRole, QByteArrayLiteral("id")},
0038         {OriginalIdRole, QByteArrayLiteral("originalId")},
0039         {UrlRole, QByteArrayLiteral("url")},
0040         {ContentRole, QByteArrayLiteral("content")},
0041         {SpoilerTextRole, QByteArrayLiteral("spoilerText")},
0042         {AuthorIdentityRole, QByteArrayLiteral("authorIdentity")},
0043         {PublishedAtRole, QByteArrayLiteral("publishedAt")},
0044         {VisibilityRole, QByteArrayLiteral("visibility")},
0045         {SelectedRole, QByteArrayLiteral("selected")},
0046         {FiltersRole, QByteArrayLiteral("filters")},
0047         {RelativeTimeRole, QByteArrayLiteral("relativeTime")},
0048         {AbsoluteTimeRole, QByteArrayLiteral("absoluteTime")},
0049         {SensitiveRole, QByteArrayLiteral("sensitive")},
0050 
0051         // Additional content
0052         {CardRole, QByteArrayLiteral("card")},
0053         {ApplicationRole, QByteArrayLiteral("application")},
0054         {PollRole, QByteArrayLiteral("poll")},
0055         {MentionsRole, QByteArrayLiteral("mentions")},
0056         {AttachmentsRole, QByteArrayLiteral("attachments")},
0057 
0058         // Reblog
0059         {IsBoostedRole, "isBoosted"},
0060         {BoostAuthorIdentityRole, "boostAuthorIdentity"},
0061 
0062         // Reply
0063         {IsReplyRole, "isReply"},
0064         {ReplyAuthorIdentityRole, "replyAuthorIdentity"},
0065 
0066         // Interaction count
0067         {ReblogsCountRole, QByteArrayLiteral("reblogsCount")},
0068         {RepliesCountRole, QByteArrayLiteral("repliesCount")},
0069         {FavouritesCountRole, QByteArrayLiteral("favouritesCount")},
0070 
0071         // User self interaction
0072         {FavouritedRole, QByteArrayLiteral("favourited")},
0073         {RebloggedRole, QByteArrayLiteral("reblogged")},
0074         {MutedRole, QByteArrayLiteral("muted")},
0075         {BookmarkedRole, QByteArrayLiteral("bookmarked")},
0076         {PinnedRole, QByteArrayLiteral("pinned")},
0077 
0078         // Notification
0079         {NotificationActorIdentityRole, "notificationActorIdentity"},
0080         {TypeRole, "type"},
0081 
0082         {PostRole, "post"},
0083     };
0084 }
0085 
0086 QVariant AbstractTimelineModel::postData(Post *post, int role) const
0087 {
0088     switch (role) {
0089     case IdRole:
0090         return post->postId();
0091     case OriginalIdRole:
0092         return post->originalPostId();
0093     case MentionsRole:
0094         return post->mentions();
0095     case ContentRole:
0096         return post->content();
0097     case AuthorIdentityRole:
0098         return QVariant::fromValue<Identity *>(post->authorIdentity().get());
0099     case IsBoostedRole:
0100         return post->boosted();
0101     case BoostAuthorIdentityRole:
0102         if (post->boostIdentity()) {
0103             return QVariant::fromValue<Identity *>(post->boostIdentity().get());
0104         }
0105         return false;
0106     case IsReplyRole:
0107         return !post->inReplyTo().isEmpty();
0108     case ReplyAuthorIdentityRole:
0109         if (!post->inReplyTo().isEmpty()) {
0110             return QVariant::fromValue<Identity *>(post->replyIdentity().get());
0111         }
0112         return false;
0113     case PublishedAtRole:
0114         return post->publishedAt();
0115     case RebloggedRole:
0116         return post->reblogged();
0117     case FavouritedRole:
0118         return post->favourited();
0119     case BookmarkedRole:
0120         return post->bookmarked();
0121     case PinnedRole:
0122         return post->pinned();
0123 
0124     case FavouritesCountRole:
0125         return post->favouritesCount();
0126     case RepliesCountRole:
0127         return post->repliesCount();
0128     case ReblogsCountRole:
0129         return post->reblogsCount();
0130     case SensitiveRole:
0131         return post->sensitive();
0132     case SpoilerTextRole:
0133         return post->spoilerText();
0134     case VisibilityRole:
0135         return post->visibility();
0136     case FiltersRole:
0137         return post->filters();
0138     case AttachmentsRole:
0139         return QVariant::fromValue<QList<Attachment *>>(post->attachments());
0140     case CardRole:
0141         if (post->card().has_value()) {
0142             return QVariant::fromValue<Card>(*post->card());
0143         }
0144         return false;
0145     case ApplicationRole:
0146         if (post->application().has_value()) {
0147             return QVariant::fromValue<Application>(*post->application());
0148         }
0149         return false;
0150     case UrlRole:
0151         return QVariant::fromValue<QUrl>(post->url());
0152     case RelativeTimeRole: {
0153         return post->relativeTime();
0154     }
0155     case AbsoluteTimeRole: {
0156         return post->absoluteTime();
0157     }
0158     case PollRole:
0159         if (post->poll()) {
0160             return QVariant::fromValue<Poll>(*post->poll());
0161         }
0162         return {};
0163     case TypeRole:
0164     case NotificationActorIdentityRole:
0165         return false;
0166     case PostRole:
0167         return QVariant::fromValue<Post *>(post);
0168     }
0169 
0170     return {};
0171 }
0172 
0173 void AbstractTimelineModel::actionFavorite(const QModelIndex &index, Post *post)
0174 {
0175     if (!post->favourited()) {
0176         m_account->favorite(post);
0177         post->setFavourited(true);
0178     } else {
0179         m_account->unfavorite(post);
0180         post->setFavourited(false);
0181     }
0182 
0183     Q_EMIT dataChanged(index, index, {FavouritedRole});
0184 }
0185 
0186 void AbstractTimelineModel::actionRepeat(const QModelIndex &index, Post *post)
0187 {
0188     if (!post->reblogged()) {
0189         m_account->repeat(post);
0190         post->setReblogged(true);
0191     } else {
0192         m_account->unrepeat(post);
0193         post->setReblogged(false);
0194     }
0195 
0196     Q_EMIT dataChanged(index, index, {RebloggedRole});
0197 }
0198 
0199 void AbstractTimelineModel::actionRedraft(const QModelIndex &index, Post *post, bool isEdit)
0200 {
0201     m_account->get(m_account->apiUrl(QString("/api/v1/statuses/%1/source").arg(post->postId())), true, this, [this, post, index, isEdit](QNetworkReply *reply) {
0202         const auto postSource = QJsonDocument::fromJson(reply->readAll()).object();
0203 
0204         auto backend = new PostEditorBackend();
0205         backend->setId(post->postId());
0206         backend->setStatus(postSource["text"].toString());
0207         backend->setSpoilerText(postSource["spoiler_text"].toString());
0208         backend->setInReplyTo(post->inReplyTo());
0209         backend->setVisibility(post->visibility());
0210         backend->setLanguage(post->language());
0211         backend->setMentions(post->mentions()); // TODO: needed?
0212         backend->setSensitive(post->sensitive());
0213 
0214         Q_EMIT postSourceReady(backend, isEdit);
0215 
0216         auto attachmentBackend = backend->attachmentEditorModel();
0217         for (const auto &attachment : post->attachments()) {
0218             attachmentBackend->appendExisting(attachment);
0219         }
0220 
0221         if (isEdit) {
0222             connect(backend, &PostEditorBackend::editComplete, this, [this, post, index](QJsonObject object) {
0223                 post->fromJson(object);
0224                 Q_EMIT dataChanged(index, index);
0225             });
0226         }
0227     });
0228 }
0229 
0230 void AbstractTimelineModel::actionBookmark(const QModelIndex &index, Post *post)
0231 {
0232     if (!post->bookmarked()) {
0233         m_account->bookmark(post);
0234         post->setBookmarked(true);
0235     } else {
0236         m_account->unbookmark(post);
0237         post->setBookmarked(false);
0238     }
0239 
0240     Q_EMIT dataChanged(index, index, {BookmarkedRole});
0241 }
0242 
0243 void AbstractTimelineModel::actionPin(const QModelIndex &index, Post *post)
0244 {
0245     if (!post->pinned()) {
0246         m_account->pin(post);
0247         post->setPinned(true);
0248     } else {
0249         m_account->unpin(post);
0250         post->setPinned(false);
0251     }
0252 
0253     Q_EMIT dataChanged(index, index, {PinnedRole});
0254 }
0255 
0256 void AbstractTimelineModel::actionDelete(const QModelIndex &index, Post *post)
0257 {
0258     Q_UNUSED(index);
0259     m_account->deleteResource(m_account->apiUrl(QString("/api/v1/statuses/%1").arg(post->postId())), true, this, [](QNetworkReply *reply) {
0260         const auto postSource = QJsonDocument::fromJson(reply->readAll()).object();
0261         qDebug() << "DELETED: " << postSource;
0262     });
0263 }