Implement FeedDetailsPage

This commit is contained in:
Tobias Fella 2020-06-06 00:05:32 +02:00
parent bef7760151
commit a5a449c08b
12 changed files with 317 additions and 24 deletions

View File

@ -57,8 +57,8 @@ bool Database::migrate()
bool Database::migrateTo1()
{
qDebug() << "Migrating database to version 1";
TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Feeds (name TEXT, url TEXT, image TEXT, link TEXT, description TEXT);")));
TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Entries (feed TEXT, id TEXT UNIQUE, title TEXT, content TEXT, created INTEGER, updated INTEGER, link TEXT);")));
TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Feeds (name TEXT, url TEXT, image TEXT, link TEXT, description TEXT, deleteAfterCount INTEGER, deleteAfterType INTEGER, subscribed INTEGER, lastUpdated INTEGER, autoUpdateCount INTEGER, autoUpdateType INTEGER, notify BOOL);")));
TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Entries (feed TEXT, id TEXT UNIQUE, title TEXT, content TEXT, created INTEGER, updated INTEGER, link TEXT, read bool);")));
TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Authors (feed TEXT, id TEXT, name TEXT, uri TEXT, email TEXT);")));
TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Enclosures (feed TEXT, id TEXT, duration INTEGER, size INTEGER, title TEXT, type STRING, url STRING);")));
TRUE_OR_RETURN(execute(QStringLiteral("PRAGMA user_version = 1;")));
@ -106,7 +106,7 @@ void Database::cleanup()
int count = settings.deleteAfterCount();
int type = settings.deleteAfterType();
if(type == 0) { //Never delete Entries
if (type == 0) { // Never delete Entries
return;
}
@ -149,12 +149,19 @@ void Database::addFeed(QString url)
qDebug() << "Feed does not yet exist";
QSqlQuery query;
query.prepare(QStringLiteral("INSERT INTO Feeds VALUES (:name, :url, :image, :link, :description);"));
query.prepare(QStringLiteral("INSERT INTO Feeds VALUES (:name, :url, :image, :link, :description, :deleteAfterCount, :deleteAfterType, :subscribed, :lastUpdated, :autoUpdateCount, :autoUpdateType, :notify);"));
query.bindValue(QStringLiteral(":name"), url);
query.bindValue(QStringLiteral(":url"), url);
query.bindValue(QStringLiteral(":image"), QLatin1String(""));
query.bindValue(QStringLiteral(":link"), QLatin1String(""));
query.bindValue(QStringLiteral(":description"), QLatin1String(""));
query.bindValue(QStringLiteral(":deleteAfterCount"), 0);
query.bindValue(QStringLiteral(":deleteAfterType"), 0);
query.bindValue(QStringLiteral(":subscribed"), QDateTime::currentDateTime().toSecsSinceEpoch());
query.bindValue(QStringLiteral(":lastUpdated"), 0);
query.bindValue(QStringLiteral(":autoUpdateCount"), 0);
query.bindValue(QStringLiteral(":autoUpdateType"), 0);
query.bindValue(QStringLiteral(":notify"), false);
execute(query);
Q_EMIT feedAdded(url);

View File

@ -19,16 +19,23 @@
*/
#include "entry.h"
#include <QSqlQuery>
#include <QUrl>
Entry::Entry(QString title, QString content, QVector<Author *> authors, QDateTime created, QDateTime updated, QString link, QObject *parent)
#include "database.h"
Entry::Entry(Feed *feed, QString id, QString title, QString content, QVector<Author *> authors, QDateTime created, QDateTime updated, QString link, bool read, QObject *parent)
: QObject(parent)
, m_feed(feed)
, m_id(id)
, m_title(title)
, m_content(content)
, m_authors(authors)
, m_created(created)
, m_updated(updated)
, m_link(link)
, m_read(read)
{
}
@ -36,6 +43,11 @@ Entry::~Entry()
{
}
QString Entry::id() const
{
return m_id;
}
QString Entry::title() const
{
return m_title;
@ -66,7 +78,24 @@ QString Entry::link() const
return m_link;
}
bool Entry::read() const
{
return m_read;
}
QString Entry::baseUrl() const
{
return QUrl(m_link).adjusted(QUrl::RemovePath).toString();
}
void Entry::setRead(bool read)
{
m_read = read;
Q_EMIT readChanged(m_read);
QSqlQuery query;
query.prepare(QStringLiteral("UPDATE Entries SET read=:read WHERE id=:id AND feed=:feed"));
query.bindValue(QStringLiteral(":id"), m_id);
query.bindValue(QStringLiteral(":feed"), m_feed->url());
query.bindValue(QStringLiteral(":read"), m_read);
Database::instance().execute(query);
}

View File

@ -34,6 +34,7 @@ class Entry : public QObject
{
Q_OBJECT
Q_PROPERTY(QString id READ id CONSTANT)
Q_PROPERTY(QString title READ title CONSTANT)
Q_PROPERTY(QString content READ content CONSTANT)
Q_PROPERTY(QVector<Author *> authors READ authors CONSTANT)
@ -41,27 +42,38 @@ class Entry : public QObject
Q_PROPERTY(QDateTime updated READ updated CONSTANT)
Q_PROPERTY(QString link READ link CONSTANT)
Q_PROPERTY(QString baseUrl READ baseUrl CONSTANT)
Q_PROPERTY(bool read READ read WRITE setRead NOTIFY readChanged);
public:
Entry(QString title, QString content, QVector<Author *> authors, QDateTime created, QDateTime updated, QString link, QObject *parent = nullptr);
Entry(Feed *feed, QString id, QString title, QString content, QVector<Author *> authors, QDateTime created, QDateTime updated, QString link, bool read, QObject *parent = nullptr);
~Entry();
QString id() const;
QString title() const;
QString content() const;
QVector<Author *> authors() const;
QDateTime created() const;
QDateTime updated() const;
QString link() const;
bool read() const;
QString baseUrl() const;
void setRead(bool read);
Q_SIGNALS:
void readChanged(bool read);
private:
Feed *m_feed;
QString m_id;
QString m_title;
QString m_content;
QVector<Author *> m_authors;
QDateTime m_created;
QDateTime m_updated;
QString m_link;
bool m_read;
};
#endif // ENTRY_H

View File

@ -94,7 +94,16 @@ void EntryListModel::loadEntry(int index) const
QDateTime updated;
updated.setSecsSinceEpoch(entryQuery.value(QStringLiteral("updated")).toInt());
Entry *entry = new Entry(entryQuery.value(QStringLiteral("title")).toString(), entryQuery.value(QStringLiteral("content")).toString(), authors, created, updated, entryQuery.value(QStringLiteral("link")).toString(), nullptr);
Entry *entry = new Entry(m_feed,
entryQuery.value(QStringLiteral("id")).toString(),
entryQuery.value(QStringLiteral("title")).toString(),
entryQuery.value(QStringLiteral("content")).toString(),
authors,
created,
updated,
entryQuery.value(QStringLiteral("link")).toString(),
entryQuery.value(QStringLiteral("read")).toBool(),
nullptr);
m_entries[index] = entry;
}

View File

@ -24,7 +24,20 @@
#include "feed.h"
#include "fetcher.h"
Feed::Feed(QString url, QString name, QString image, QString link, QString description, QVector<Author *> authors, QObject *parent)
Feed::Feed(QString url,
QString name,
QString image,
QString link,
QString description,
QVector<Author *> authors,
int deleteAfterCount,
int deleteAfterType,
QDateTime subscribed,
QDateTime lastUpdated,
int autoUpdateCount,
int autoUpdateType,
bool notify,
QObject *parent)
: QObject(parent)
, m_url(url)
, m_name(name)
@ -32,15 +45,24 @@ Feed::Feed(QString url, QString name, QString image, QString link, QString descr
, m_link(link)
, m_description(description)
, m_authors(authors)
, m_deleteAfterCount(deleteAfterCount)
, m_deleteAfterType(deleteAfterType)
, m_subscribed(subscribed)
, m_lastUpdated(lastUpdated)
, m_autoUpdateCount(autoUpdateCount)
, m_autoUpdateType(autoUpdateType)
, m_notify(notify)
{
connect(&Fetcher::instance(), &Fetcher::startedFetchingFeed, this, [this] (QString url) {
if(url == m_url) {
connect(&Fetcher::instance(), &Fetcher::startedFetchingFeed, this, [this](QString url) {
if (url == m_url) {
setRefreshing(true);
}
});
connect(&Fetcher::instance(), &Fetcher::feedUpdated, this, [this] (QString url) {
if(url == m_url) {
connect(&Fetcher::instance(), &Fetcher::feedUpdated, this, [this](QString url) {
if (url == m_url) {
setRefreshing(false);
emit entryCountChanged();
emit unreadEntryCountChanged();
}
});
}
@ -79,6 +101,63 @@ QVector<Author *> Feed::authors() const
return m_authors;
}
int Feed::deleteAfterCount() const
{
return m_deleteAfterCount;
}
int Feed::deleteAfterType() const
{
return m_deleteAfterType;
}
QDateTime Feed::subscribed() const
{
return m_subscribed;
}
QDateTime Feed::lastUpdated() const
{
return m_lastUpdated;
}
int Feed::autoUpdateCount() const
{
return m_autoUpdateCount;
}
int Feed::autoUpdateType() const
{
return m_autoUpdateType;
}
bool Feed::notify() const
{
return m_notify;
}
int Feed::entryCount() const
{
QSqlQuery query;
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries where feed=:feed;"));
query.bindValue(QStringLiteral(":feed"), m_url);
Database::instance().execute(query);
if (!query.next())
return -1;
return query.value(0).toInt();
}
int Feed::unreadEntryCount() const
{
QSqlQuery query;
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries where feed=:feed AND read=false;"));
query.bindValue(QStringLiteral(":feed"), m_url);
Database::instance().execute(query);
if (!query.next())
return -1;
return query.value(0).toInt();
}
bool Feed::refreshing() const
{
return m_refreshing;
@ -114,6 +193,42 @@ void Feed::setAuthors(QVector<Author *> authors)
Q_EMIT authorsChanged(m_authors);
}
void Feed::setDeleteAfterCount(int count)
{
m_deleteAfterCount = count;
Q_EMIT deleteAfterCountChanged(m_deleteAfterCount);
}
void Feed::setDeleteAfterType(int type)
{
m_deleteAfterType = type;
Q_EMIT deleteAfterTypeChanged(m_deleteAfterType);
}
void Feed::setLastUpdated(QDateTime lastUpdated)
{
m_lastUpdated = lastUpdated;
Q_EMIT lastUpdatedChanged(m_lastUpdated);
}
void Feed::setAutoUpdateCount(int count)
{
m_autoUpdateCount = count;
Q_EMIT autoUpdateCountChanged(m_autoUpdateCount);
}
void Feed::setAutoUpdateType(int type)
{
m_autoUpdateType = type;
Q_EMIT autoUpdateTypeChanged(m_autoUpdateType);
}
void Feed::setNotify(bool notify)
{
m_notify = notify;
Q_EMIT notifyChanged(m_notify);
}
void Feed::setRefreshing(bool refreshing)
{
m_refreshing = refreshing;

View File

@ -21,6 +21,7 @@
#ifndef FEED_H
#define FEED_H
#include <QDateTime>
#include <QObject>
#include "author.h"
@ -36,9 +37,31 @@ class Feed : public QObject
Q_PROPERTY(QString description READ description WRITE setDescription NOTIFY descriptionChanged)
Q_PROPERTY(QVector<Author *> authors READ authors WRITE setAuthors NOTIFY authorsChanged)
Q_PROPERTY(bool refreshing READ refreshing WRITE setRefreshing NOTIFY refreshingChanged)
Q_PROPERTY(int deleteAfterCount READ deleteAfterCount WRITE setDeleteAfterCount NOTIFY deleteAfterCountChanged)
Q_PROPERTY(int deleteAfterType READ deleteAfterType WRITE setDeleteAfterType NOTIFY deleteAfterTypeChanged)
Q_PROPERTY(QDateTime subscribed READ subscribed CONSTANT)
Q_PROPERTY(QDateTime lastUpdated READ lastUpdated WRITE setLastUpdated NOTIFY lastUpdatedChanged)
Q_PROPERTY(int autoUpdateCount READ autoUpdateCount WRITE setAutoUpdateCount NOTIFY autoUpdateCountChanged)
Q_PROPERTY(int autoUpdateType READ autoUpdateType WRITE setAutoUpdateType NOTIFY autoUpdateCountChanged)
Q_PROPERTY(bool notify READ notify WRITE setNotify NOTIFY notifyChanged)
Q_PROPERTY(int entryCount READ entryCount NOTIFY entryCountChanged)
Q_PROPERTY(int unreadEntryCount READ unreadEntryCount NOTIFY unreadEntryCountChanged)
public:
Feed(QString url, QString name, QString image, QString link, QString description, QVector<Author *> authors, QObject *parent = nullptr);
Feed(QString url,
QString name,
QString image,
QString link,
QString description,
QVector<Author *> authors,
int deleteAfterCount,
int deleteAfterType,
QDateTime subscribed,
QDateTime lastUpdated,
int autoUpdateCount,
int autoUpdateType,
bool notify,
QObject *parent = nullptr);
~Feed();
@ -48,6 +71,17 @@ public:
QString link() const;
QString description() const;
QVector<Author *> authors() const;
int deleteAfterCount() const;
int deleteAfterType() const;
QDateTime subscribed() const;
QDateTime lastUpdated() const;
int autoUpdateCount() const;
int autoUpdateType() const;
bool notify() const;
int entryCount() const;
int unreadEntryCount() const;
bool read() const;
bool refreshing() const;
void setName(QString name);
@ -55,6 +89,12 @@ public:
void setLink(QString link);
void setDescription(QString description);
void setAuthors(QVector<Author *> authors);
void setDeleteAfterCount(int count);
void setDeleteAfterType(int type);
void setLastUpdated(QDateTime lastUpdated);
void setAutoUpdateCount(int count);
void setAutoUpdateType(int type);
void setNotify(bool notify);
void setRefreshing(bool refreshing);
Q_INVOKABLE void refresh();
@ -66,6 +106,15 @@ Q_SIGNALS:
void linkChanged(QString &link);
void descriptionChanged(QString &description);
void authorsChanged(QVector<Author *> &authors);
void deleteAfterCountChanged(int count);
void deleteAfterTypeChanged(int type);
void lastUpdatedChanged(QDateTime lastUpdated);
void autoUpdateCountChanged(int count);
void autoUpdateTypeChanged(int type);
void notifyChanged(bool notify);
void entryCountChanged();
void unreadEntryCountChanged();
void refreshingChanged(bool refreshing);
private:
@ -75,6 +124,14 @@ private:
QString m_link;
QString m_description;
QVector<Author *> m_authors;
int m_deleteAfterCount;
int m_deleteAfterType;
QDateTime m_subscribed;
QDateTime m_lastUpdated;
int m_autoUpdateCount;
int m_autoUpdateType;
bool m_notify;
bool m_refreshing = false;
};

View File

@ -35,13 +35,14 @@ FeedListModel::FeedListModel(QObject *parent)
beginInsertRows(QModelIndex(), rowCount(QModelIndex()) - 1, rowCount(QModelIndex()) - 1);
endInsertRows();
});
connect(&Fetcher::instance(), &Fetcher::feedDetailsUpdated, this, [this](QString url, QString name, QString image, QString link, QString description) {
connect(&Fetcher::instance(), &Fetcher::feedDetailsUpdated, this, [this](QString url, QString name, QString image, QString link, QString description, QDateTime lastUpdated) {
for (int i = rowCount(QModelIndex()) - 1; i >= 0; i--) {
if (m_feeds[i]->url() == url) {
m_feeds[i]->setName(name);
m_feeds[i]->setImage(image);
m_feeds[i]->setLink(link);
m_feeds[i]->setDescription(description);
m_feeds[i]->setLastUpdated(lastUpdated);
Q_EMIT dataChanged(createIndex(i, 0), createIndex(i, 0));
break;
}
@ -94,7 +95,26 @@ void FeedListModel::loadFeed(int index) const
authors += new Author(authorQuery.value(QStringLiteral("name")).toString(), authorQuery.value(QStringLiteral("email")).toString(), authorQuery.value(QStringLiteral("uri")).toString(), nullptr);
}
Feed *feed = new Feed(query.value(QStringLiteral("url")).toString(), query.value(QStringLiteral("name")).toString(), query.value(QStringLiteral("image")).toString(), query.value(QStringLiteral("link")).toString(), query.value(QStringLiteral("description")).toString(), authors, nullptr);
QDateTime subscribed;
subscribed.setSecsSinceEpoch(query.value(QStringLiteral("subscribed")).toInt());
QDateTime lastUpdated;
lastUpdated.setSecsSinceEpoch(query.value(QStringLiteral("lastUpdated")).toInt());
Feed *feed = new Feed(query.value(QStringLiteral("url")).toString(),
query.value(QStringLiteral("name")).toString(),
query.value(QStringLiteral("image")).toString(),
query.value(QStringLiteral("link")).toString(),
query.value(QStringLiteral("description")).toString(),
authors,
query.value(QStringLiteral("deleteAfterCount")).toInt(),
query.value(QStringLiteral("deleteAfterType")).toInt(),
subscribed,
lastUpdated,
query.value(QStringLiteral("autoUpdateCount")).toInt(),
query.value(QStringLiteral("autoUpdateType")).toInt(),
query.value(QStringLiteral("notify")).toBool(),
nullptr);
m_feeds[index] = feed;
}
@ -110,7 +130,7 @@ void FeedListModel::removeFeed(int index)
void FeedListModel::refreshAll()
{
for(auto &feed : m_feeds) {
for (auto &feed : m_feeds) {
feed->refresh();
}
}

View File

@ -18,6 +18,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <QDateTime>
#include <QFile>
#include <QFileInfo>
#include <QNetworkAccessManager>
@ -62,7 +63,7 @@ void Fetcher::fetchAll()
QSqlQuery query;
query.prepare(QStringLiteral("SELECT url FROM Feeds;"));
Database::instance().execute(query);
while(query.next()) {
while (query.next()) {
fetch(query.value(0).toString());
}
}
@ -73,13 +74,16 @@ void Fetcher::processFeed(Syndication::FeedPtr feed, QString url)
return;
QSqlQuery query;
query.prepare(QStringLiteral("UPDATE Feeds SET name=:name, image=:image, link=:link, description=:description WHERE url=:url;"));
query.prepare(QStringLiteral("UPDATE Feeds SET name=:name, image=:image, link=:link, description=:description, lastUpdated=:lastUpdated WHERE url=:url;"));
query.bindValue(QStringLiteral(":name"), feed->title());
query.bindValue(QStringLiteral(":url"), url);
query.bindValue(QStringLiteral(":link"), feed->link());
query.bindValue(QStringLiteral(":description"), feed->description());
for(auto &author : feed->authors()) {
QDateTime current = QDateTime::currentDateTime();
query.bindValue(QStringLiteral(":lastUpdated"), current.toSecsSinceEpoch());
for (auto &author : feed->authors()) {
processAuthor(author, QLatin1String(""), url);
}
@ -93,7 +97,7 @@ void Fetcher::processFeed(Syndication::FeedPtr feed, QString url)
qDebug() << "Updated feed title:" << feed->title();
Q_EMIT feedDetailsUpdated(url, feed->title(), image, feed->link(), feed->description());
Q_EMIT feedDetailsUpdated(url, feed->title(), image, feed->link(), feed->description(), current);
for (const auto &entry : feed->items()) {
processEntry(entry, url);
@ -114,7 +118,7 @@ void Fetcher::processEntry(Syndication::ItemPtr entry, QString url)
if (query.value(0).toInt() != 0)
return;
query.prepare(QStringLiteral("INSERT INTO Entries VALUES (:feed, :id, :title, :content, :created, :updated, :link);"));
query.prepare(QStringLiteral("INSERT INTO Entries VALUES (:feed, :id, :title, :content, :created, :updated, :link, false);"));
query.bindValue(QStringLiteral(":feed"), url);
query.bindValue(QStringLiteral(":id"), entry->id());
query.bindValue(QStringLiteral(":title"), QTextDocumentFragment::fromHtml(entry->title()).toPlainText());

View File

@ -54,5 +54,5 @@ private:
Q_SIGNALS:
void startedFetchingFeed(QString url);
void feedUpdated(QString url);
void feedDetailsUpdated(QString url, QString name, QString image, QString link, QString description);
void feedDetailsUpdated(QString url, QString name, QString image, QString link, QString description, QDateTime lastUpdated);
};

View File

@ -36,6 +36,7 @@ Kirigami.SwipeListItem {
Layout.fillWidth: true
elide: Text.ElideRight
opacity: 1
color: model.entry.read ? Kirigami.Theme.disabledTextColor : Kirigami.Theme.textColor
}
Controls.Label {
id: subtitleItem
@ -45,10 +46,12 @@ Kirigami.SwipeListItem {
font: Kirigami.Theme.smallFont
opacity: 0.6
visible: text.length > 0
color: model.entry.read ? Kirigami.Theme.disabledTextColor : Kirigami.Theme.textColor
}
}
onClicked: {
model.entry.read = true
pageStack.push("qrc:/EntryPage.qml", {"entry": model.entry})
}
}

View File

@ -44,7 +44,7 @@ Kirigami.ScrollablePage {
Kirigami.Action {
iconName: "help-about-symbolic"
text: i18n("Details")
onTriggered: pageStack.push("qrc:/FeedDetailsPage.qml")
onTriggered: pageStack.push("qrc:/FeedDetailsPage.qml", {"feed": feed})
}
]

View File

@ -24,7 +24,44 @@ import QtQuick.Layouts 1.14
import org.kde.kirigami 2.12 as Kirigami
import org.kde.alligator 1.0
Kirigami.Page {
Kirigami.ScrollablePage {
id: detailsPage
property QtObject feed;
title: i18nc("<Feed Name> - Details", "%1 - Details", feed.name)
ColumnLayout {
Kirigami.Icon {
source: Fetcher.image(feed.image)
height: 200
width: height
}
Kirigami.Heading {
text: feed.name
}
Kirigami.Heading {
text: feed.description;
level: 3
}
Controls.Label {
text: i18nc("by <author(s)>", "by %1", feed.authors[0].name)
visible: feed.authors.length !== 0
}
Controls.Label {
text: "<a href='%1'>%1</a>".arg(feed.link)
onLinkActivated: Qt.openUrlExternally(link)
}
Controls.Label {
text: i18n("Subscribed since: %1", feed.subscribed.toLocaleString(Qt.locale(), Locale.ShortFormat))
}
Controls.Label {
text: i18n("last updated: %1", feed.lastUpdated.toLocaleString(Qt.locale(), Locale.ShortFormat))
}
Controls.Label {
text: i18n("%1 posts, %2 unread", feed.entryCount, feed.unreadEntryCount)
}
}
}