port models to QSqlTableModel

This commit is contained in:
Tobias Fella 2020-04-18 21:07:49 +02:00
parent ff7c03a32c
commit eb8d24e28a
15 changed files with 109 additions and 411 deletions

View File

@ -3,8 +3,6 @@ set(alligator_SRCS
feedListModel.cpp feedListModel.cpp
entryListModel.cpp entryListModel.cpp
fetcher.cpp fetcher.cpp
feed.cpp
entry.cpp
database.cpp database.cpp
resources.qrc resources.qrc
) )

View File

@ -46,12 +46,12 @@ Database::Database()
} }
bool Database::migrate() { bool Database::migrate() {
qDebug() << "Migrating database";
if(version() < 1) TRUE_OR_RETURN(migrateTo1()); if(version() < 1) TRUE_OR_RETURN(migrateTo1());
return true; return true;
} }
bool Database::migrateTo1() { bool Database::migrateTo1() {
qDebug() << "Migrating database to version 1";
QSqlQuery query(QSqlDatabase::database()); QSqlQuery query(QSqlDatabase::database());
TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Feeds (name TEXT, url TEXT, image TEXT);"))); TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Feeds (name TEXT, url TEXT, image 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);"))); TRUE_OR_RETURN(execute(QStringLiteral("CREATE TABLE IF NOT EXISTS Entries (feed TEXT, id TEXT UNIQUE, title TEXT, content TEXT, created INTEGER, updated INTEGER);")));

View File

@ -1,74 +0,0 @@
/**
* Copyright 2020 Tobias Fella <fella@posteo.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "entry.h"
Entry::Entry(const QString title, const QString content, const int updated, const bool bookmark, const bool read)
: m_title(title)
, m_content(content)
, m_updated(updated)
, m_bookmark(bookmark)
, m_read(read)
{
}
Entry::Entry(const Entry &other)
: m_title(other.title())
, m_content(other.content())
, m_updated(other.updated())
, m_bookmark(other.isBookmark())
, m_read(other.isRead())
{
}
bool Entry::isRead() const
{
return m_read;
}
bool Entry::isBookmark() const
{
return m_bookmark;
}
QString Entry::title() const
{
return m_title;
}
QString Entry::content() const
{
return m_content;
}
int Entry::updated() const
{
return m_updated;
}
void Entry::setRead(bool read)
{
m_read = read;
}
void Entry::setBookmark(bool bookmark)
{
m_bookmark = bookmark;
}

View File

@ -1,46 +0,0 @@
/**
* Copyright 2020 Tobias Fella <fella@posteo.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QString>
class Entry
{
public:
Entry(const Entry &);
Entry(const QString title, const QString content, const int updated, const bool bookmark, const bool read);
QString title() const;
QString content() const;
int updated() const;
bool isBookmark() const;
bool isRead() const;
void setRead(bool read);
void setBookmark(bool bookmark);
private:
QString m_title;
QString m_content;
int m_updated;
bool m_bookmark;
bool m_read;
};

View File

@ -26,87 +26,49 @@
#include "database.h" #include "database.h"
EntryListModel::EntryListModel(QObject *parent) EntryListModel::EntryListModel(QObject *parent)
: QAbstractListModel(parent) : QSqlTableModel(parent)
{ {
setTable("entries");
setSort(Updated, Qt::DescendingOrder);
setEditStrategy(OnFieldChange);
select();
} }
QVariant EntryListModel::data(const QModelIndex &index, int role) const QVariant EntryListModel::data(const QModelIndex &index, int role) const
{ {
if (role == Title) if(role == Updated || role == Created) {
return m_entries[index.row()].title();
if (role == Content)
return m_entries[index.row()].content();
if (role == Updated) {
QDateTime updated; QDateTime updated;
updated.setSecsSinceEpoch(m_entries[index.row()].updated()); updated.setSecsSinceEpoch(QSqlQueryModel::data(createIndex(index.row(), role), 0).toInt());
return updated; return updated;
} }
if (role == Bookmark) return QSqlQueryModel::data(createIndex(index.row(), role), 0);
return m_entries[index.row()].isBookmark(); }
if (role == Read)
return m_entries[index.row()].isRead();
return QStringLiteral("DEADBEEF");
}
int EntryListModel::rowCount(const QModelIndex &index) const
{
return m_entries.size();
}
QHash<int, QByteArray> EntryListModel::roleNames() const QHash<int, QByteArray> EntryListModel::roleNames() const
{ {
QHash<int, QByteArray> roleNames; QHash<int, QByteArray> roleNames;
roleNames[Feed] = "feed";
roleNames[Id] = "id";
roleNames[Title] = "title"; roleNames[Title] = "title";
roleNames[Content] = "content"; roleNames[Content] = "content";
roleNames[Created] = "created";
roleNames[Updated] = "updated"; roleNames[Updated] = "updated";
roleNames[Bookmark] = "bookmark";
roleNames[Read] = "read";
return roleNames; return roleNames;
} }
bool EntryListModel::setData(const QModelIndex &index, const QVariant &value, int role)
void EntryListModel::setFeed(QString url)
{ {
if (role == Bookmark) { m_feed = url;
m_entries[index.row()].setBookmark(value.toBool()); setFilter(QStringLiteral("feed ='%1'").arg(url));
} else if (role == Read) { select();
m_entries[index.row()].setRead(value.toBool());
}
emit dataChanged(index, index, QVector<int>({role}));
return true;
} }
void EntryListModel::fetch() QString EntryListModel::feed() const
{
connect(&Fetcher::instance(), &Fetcher::finished, this, &EntryListModel::update);
if(m_feed.url().compare("all") != 0)
Fetcher::instance().fetch(m_feed.url());
else
update();
}
void EntryListModel::update() {
beginResetModel();
QSqlQuery query;
if(m_feed.url().compare("all") == 0) {
query.prepare(QStringLiteral("SELECT id, title, content, updated FROM Entries ORDER BY updated DESC;"));
}
else {
query.prepare(QStringLiteral("SELECT id, title, content, updated FROM Entries WHERE feed=:feed ORDER BY updated DESC;"));
query.bindValue(QStringLiteral(":feed"), m_feed.url());
}
Database::instance().execute(query);
while (query.next()) {
m_entries.append(Entry(query.value(1).toString(), query.value(2).toString(), query.value(3).toInt(), false, false));
}
endResetModel();
}
Feed EntryListModel::feed()
{ {
return m_feed; return m_feed;
} }
void EntryListModel::setFeed(Feed feed) void EntryListModel::fetch()
{ {
m_feed = feed; Fetcher::instance().fetch(m_feed);
emit feedChanged(feed);
} }

View File

@ -20,41 +20,32 @@
#pragma once #pragma once
#include <QAbstractListModel> #include <QSqlTableModel>
#include <QObject> #include <QObject>
#include <QString>
#include "entry.h" class EntryListModel : public QSqlTableModel
#include "feed.h"
class EntryListModel : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(Feed feed READ feed WRITE setFeed NOTIFY feedChanged) Q_PROPERTY(QString feed READ feed WRITE setFeed)
public: public:
enum DataRole { enum DataRole {
Title = Qt::UserRole + 1, Feed = 0,
Id,
Title,
Content, Content,
Created,
Updated, Updated,
Bookmark,
Read,
}; };
explicit EntryListModel(QObject *parent = nullptr); explicit EntryListModel(QObject *parent = nullptr);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &index) const override;
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
Feed feed();
void setFeed(Feed feed);
Q_INVOKABLE void fetch(); Q_INVOKABLE void fetch();
Q_SIGNALS: QString feed() const;
void feedChanged(Feed); void setFeed(QString feed);
private: private:
QVector<Entry> m_entries; QString m_feed;
Feed m_feed;
void update();
}; };

View File

@ -1,79 +0,0 @@
/**
* Copyright 2020 Tobias Fella <fella@posteo.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <QVariant>
#include "feed.h"
Feed::Feed()
: m_url(QStringLiteral(""))
, m_name(QStringLiteral(""))
, m_image(QStringLiteral(""))
{
}
Feed::Feed(const QString url)
: m_url(url)
, m_name(url)
, m_image(QStringLiteral(""))
{
}
Feed::Feed(const QString url, const QString name, const QString image)
: m_url(url)
, m_name(name)
, m_image(image)
{
}
Feed::Feed(const Feed &other)
: m_url(other.url())
, m_name(other.name())
, m_image(other.image())
{
}
Feed::~Feed()
{
}
QString Feed::url() const
{
return m_url;
}
QString Feed::name() const
{
return m_name;
}
QString Feed::image() const
{
return m_image;
}
void Feed::setName(QString name)
{
m_name = name;
}
void Feed::setImage(QString image)
{
m_image = image;
}

View File

@ -1,53 +0,0 @@
/**
* Copyright 2020 Tobias Fella <fella@posteo.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QObject>
#include <QString>
class Feed
{
Q_GADGET
Q_PROPERTY(QString url READ url)
Q_PROPERTY(QString name READ name WRITE setName)
Q_PROPERTY(QString image READ image WRITE setImage)
public:
Feed();
Feed(const QString url);
Feed(const QString url, const QString name, const QString image);
Feed(const Feed &other);
~Feed();
QString name() const;
QString url() const;
QString image() const;
private:
QString m_url;
QString m_name;
QString m_image;
void setName(QString);
void setImage(QString);
};
Q_DECLARE_METATYPE(Feed);

View File

@ -19,86 +19,77 @@
*/ */
#include <QUrl> #include <QUrl>
#include <QSqlRecord>
#include <QDebug>
#include <QModelIndex>
#include <QSqlError>
#include "feedListModel.h" #include "feedListModel.h"
#include "fetcher.h" #include "fetcher.h"
#include "database.h" #include "database.h"
FeedListModel::FeedListModel(QObject *parent) FeedListModel::FeedListModel(QObject *parent)
: QAbstractListModel(parent) : QSqlTableModel(parent)
{ {
QSqlQuery query; setTable("Feeds");
query.prepare(QStringLiteral("SELECT name, url, image FROM Feeds")); setSort(0, Qt::AscendingOrder);
Database::instance().execute(query); setEditStrategy(OnFieldChange);
beginInsertRows(QModelIndex(), 0, query.size()); select();
while (query.next()) {
feeds += Feed(query.value(1).toString(), query.value(0).toString(), query.value(2).toString());
}
endInsertRows();
} }
QHash<int, QByteArray> FeedListModel::roleNames() const QHash<int, QByteArray> FeedListModel::roleNames() const
{ {
QHash<int, QByteArray> roleNames; QHash<int, QByteArray> roleNames;
roleNames[FeedRole] = "feed"; roleNames[Name] = "name";
roleNames[Url] = "url";
roleNames[Image] = "image";
return roleNames; return roleNames;
} }
QVariant FeedListModel::data(const QModelIndex &index, int role) const
{
if (role == FeedRole) {
return QVariant::fromValue(feeds[index.row()]);
}
return QStringLiteral("DEADBEEF");
}
int FeedListModel::rowCount(const QModelIndex &index) const
{
return feeds.size();
}
void FeedListModel::addFeed(QString url) void FeedListModel::addFeed(QString url)
{ {
qDebug() << "Adding feed";
if(feedExists(url)) {
qDebug() << "Feed already exists";
return;
}
qDebug() << "Feed does not yet exist";
QSqlRecord rec = record();
rec.setValue(0, url);
rec.setValue(1, url);
rec.setValue(2, "");
insertRecord(-1, rec);
connect(&Fetcher::instance(), &Fetcher::updated, this, [this]() {
select();
disconnect(&Fetcher::instance(), &Fetcher::updated, nullptr, nullptr);
});
Fetcher::instance().fetch(QUrl(url));
}
QVariant FeedListModel::data(const QModelIndex &index, int role) const
{
return QSqlTableModel::data(createIndex(index.row(), role), 0);
}
bool FeedListModel::feedExists(QString url) {
QSqlQuery query; QSqlQuery query;
query.prepare(QStringLiteral("SELECT COUNT (url) FROM Feeds WHERE url=:url;")); query.prepare(QStringLiteral("SELECT COUNT (url) FROM Feeds WHERE url=:url;"));
query.bindValue(QStringLiteral(":url"), url); query.bindValue(QStringLiteral(":url"), url);
Database::instance().execute(query); Database::instance().execute(query);
query.next(); query.next();
if(query.value(0).toInt() != 0) return; return query.value(0).toInt() != 0;
connect(&Fetcher::instance(), &Fetcher::finished, this, [this, url]() {
QSqlQuery query;
query.prepare(QStringLiteral("SELECT name, image FROM Feeds WHERE url=:url;"));
query.bindValue(QStringLiteral(":url"), url);
Database::instance().execute(query);
query.next();
for(int i = 0; i < feeds.length(); i++) {
if(feeds[i].url() == url) {
feeds.removeAt(i);
feeds.insert(i, Feed(url, query.value(0).toString(), query.value(1).toString()));
emit dataChanged(index(i), index(i));
break;
}
}
});
Fetcher::instance().fetch(QUrl(url));
beginInsertRows(QModelIndex(), feeds.size(), feeds.size());
feeds.append(Feed(url));
endInsertRows();
query.prepare(QStringLiteral("INSERT INTO Feeds VALUES (:url, :name, '');"));
query.bindValue(QStringLiteral(":url"), url);
query.bindValue(QStringLiteral(":name"), url);
Database::instance().execute(query);
} }
void FeedListModel::removeFeed(int index) void FeedListModel::removeFeed(int index)
{ {
Feed toRemove = feeds[index]; //Workaround...
QSqlQuery query; QSqlQuery query;
query.prepare(QStringLiteral("DELETE FROM Feeds WHERE name=:name AND url=url;")); query.prepare("DELETE FROM Feeds WHERE url=:url");
query.bindValue(QStringLiteral(":url"), toRemove.url()); query.bindValue(":url", data(createIndex(index, 0), 1).toString());
query.bindValue(QStringLiteral(":name"), toRemove.name());
Database::instance().execute(query); Database::instance().execute(query);
beginRemoveRows(QModelIndex(), index, index); select();
feeds.remove(index);
endRemoveRows();
} }

View File

@ -20,29 +20,30 @@
#pragma once #pragma once
#include <QAbstractListModel> #include <QSqlTableModel>
#include <QUrl> #include <QUrl>
#include <QVector> #include <QVector>
#include "feed.h" //#include "feed.h"
#include "fetcher.h" #include "fetcher.h"
class FeedListModel : public QAbstractListModel class FeedListModel : public QSqlTableModel
{ {
Q_OBJECT Q_OBJECT
public: public:
enum DataRole { enum DataRole {
FeedRole = Qt::UserRole + 1, Name = 0,
Url,
Image,
}; };
explicit FeedListModel(QObject *parent = nullptr); explicit FeedListModel(QObject *parent = nullptr);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &index) const override;
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE void addFeed(QString url); Q_INVOKABLE void addFeed(QString url);
Q_INVOKABLE void removeFeed(int index); Q_INVOKABLE void removeFeed(int index);
private: private:
QVector<Feed> feeds; bool feedExists(QString url);
}; };

View File

@ -31,6 +31,7 @@ Fetcher::Fetcher() {
void Fetcher::fetch(QUrl url) void Fetcher::fetch(QUrl url)
{ {
qDebug() << "Starting to fetch" << url.toString();
QNetworkAccessManager *manager = new QNetworkAccessManager(this); QNetworkAccessManager *manager = new QNetworkAccessManager(this);
manager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); manager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
manager->setStrictTransportSecurityEnabled(true); manager->setStrictTransportSecurityEnabled(true);
@ -47,7 +48,15 @@ void Fetcher::fetch(QUrl url)
QSqlQuery query; QSqlQuery query;
query.prepare(QStringLiteral("UPDATE Feeds SET name=:name, image=:image WHERE url=:url;"));
query.bindValue(QStringLiteral(":name"), feed->title());
query.bindValue(QStringLiteral(":url"), url.toString());
query.bindValue(QStringLiteral(":image"), feed->image()->url());
Database::instance().execute(query);
qDebug() << "Updated feed title:" << feed->title();
for (const auto &entry : feed->items()) { for (const auto &entry : feed->items()) {
qDebug() << "Processing" << entry->title();
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries WHERE id=:id;")); query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries WHERE id=:id;"));
query.bindValue(QStringLiteral(":id"), entry->id()); query.bindValue(QStringLiteral(":id"), entry->id());
Database::instance().execute(query); Database::instance().execute(query);
@ -72,13 +81,9 @@ void Fetcher::fetch(QUrl url)
query.bindValue(QStringLiteral(":email"), author->email()); query.bindValue(QStringLiteral(":email"), author->email());
Database::instance().execute(query); Database::instance().execute(query);
} }
query.prepare(QStringLiteral("UPDATE Feeds SET name=:name, image=:image WHERE url=:url;"));
query.bindValue(QStringLiteral(":name"), feed->title());
query.bindValue(QStringLiteral(":url"), url.toString());
query.bindValue(QStringLiteral(":image"), feed->image()->url());
Database::instance().execute(query);
} }
emit updated();
delete reply; delete reply;
emit finished();
}); });
} }

View File

@ -39,5 +39,5 @@ private:
Fetcher(const Fetcher &); Fetcher(const Fetcher &);
Q_SIGNALS: Q_SIGNALS:
void finished(); void updated();
}; };

View File

@ -43,7 +43,6 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
qmlRegisterType<FeedListModel>("org.kde.alligator", 1, 0, "FeedListModel"); qmlRegisterType<FeedListModel>("org.kde.alligator", 1, 0, "FeedListModel");
qmlRegisterType<EntryListModel>("org.kde.alligator", 1, 0, "EntryListModel"); qmlRegisterType<EntryListModel>("org.kde.alligator", 1, 0, "EntryListModel");
qRegisterMetaType<Feed>();
QQmlApplicationEngine engine; QQmlApplicationEngine engine;

View File

@ -28,17 +28,19 @@ import org.kde.alligator 1.0
Kirigami.ScrollablePage { Kirigami.ScrollablePage {
id: page id: page
property var feed
property var data
property var all: page.data.url === "all"
contextualActions: [ contextualActions: [
Kirigami.Action { Kirigami.Action {
text: i18n("Details") text: i18n("Details")
visible: feed.url != "all" visible: !all
onTriggered: ;//pageStack.push("qrc:/qml/FeedDetailsPage.qml", {"modelData": atomModel}) onTriggered: ;//pageStack.push("qrc:/qml/FeedDetailsPage.qml", {"modelData": atomModel})
} }
] ]
Component.onCompleted: { Component.onCompleted: {
entryListModel.fetch(); entryListModel.fetch();
} }
@ -46,27 +48,28 @@ Kirigami.ScrollablePage {
ListView { ListView {
model: EntryListModel { model: EntryListModel {
id: entryListModel id: entryListModel
feed: page.feed feed: page.data.url
} }
header: RowLayout { header: RowLayout {
width: parent.width width: parent.width
height: page.height * 0.2 height: page.height * 0.2
visible: !all
Image { Image {
source: feed.image source: page.data.image
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
sourceSize.width: 0 sourceSize.width: 0
sourceSize.height: parent.height sourceSize.height: parent.height
} }
Kirigami.Heading { Kirigami.Heading {
text: feed.name text: page.data.name
} }
} }
delegate: Kirigami.SwipeListItem { delegate: Kirigami.SwipeListItem {
Controls.Label { Controls.Label {
width: parent.width width: parent.width
text: model.title + " - " + model.updated text: model.title + " - " + model.updated.toLocaleString(Qt.locale(), Locale.ShortFormat)
textFormat: Text.RichText textFormat: Text.RichText
color: model.read ? Kirigami.Theme.disabledTextColor : Kirigami.Theme.textColor color: model.read ? Kirigami.Theme.disabledTextColor : Kirigami.Theme.textColor
} }

View File

@ -86,13 +86,13 @@ Kirigami.ScrollablePage {
Item { Item {
Kirigami.Icon { Kirigami.Icon {
id: icon id: icon
source: model.feed.image source: model.image
width: height width: height
height: parent.height height: parent.height
} }
Controls.Label { Controls.Label {
text: model.feed.name text: model.name
height: parent.height height: parent.height
anchors.left: icon.right anchors.left: icon.right
leftPadding: 0.5*Kirigami.Units.gridUnit leftPadding: 0.5*Kirigami.Units.gridUnit
@ -107,7 +107,7 @@ Kirigami.ScrollablePage {
] ]
onClicked: pageStack.push("qrc:/EntryListPage.qml", {"feed": feed}) onClicked: pageStack.push("qrc:/EntryListPage.qml", {"data": model})
} }
} }
} }