Add first version of EpisodeListPage

This commit is contained in:
Bart De Vries 2021-04-17 22:54:35 +02:00
parent 62440e8609
commit 130b10aefb
10 changed files with 229 additions and 1 deletions

View File

@ -10,6 +10,7 @@ add_executable(alligator
enclosure.cpp enclosure.cpp
enclosuredownloadjob.cpp enclosuredownloadjob.cpp
queuemodel.cpp queuemodel.cpp
episodemodel.cpp
datamanager.cpp datamanager.cpp
audiomanager.cpp audiomanager.cpp
powermanagementinterface.cpp powermanagementinterface.cpp

View File

@ -138,6 +138,28 @@ Entry* DataManager::getEntry(QString id) const
return m_entries[id]; return m_entries[id];
} }
Entry* DataManager::getEntry(const EpisodeModel::Type type, const int entry_index) const
{
QSqlQuery entryQuery;
if (type == EpisodeModel::All || type == EpisodeModel::New) {
if (type == EpisodeModel::New) {
entryQuery.prepare(QStringLiteral("SELECT * FROM Entries WHERE new=:new ORDER BY updated DESC LIMIT 1 OFFSET :index;"));
entryQuery.bindValue(QStringLiteral(":new"), true);
} else { // i.e. EpisodeModel::All
entryQuery.prepare(QStringLiteral("SELECT * FROM Entries ORDER BY updated DESC LIMIT 1 OFFSET :index;"));
}
entryQuery.bindValue(QStringLiteral(":index"), entry_index);
Database::instance().execute(entryQuery);
if (!entryQuery.next()) {
qWarning() << "No element with index" << entry_index << "found";
return nullptr;
}
QString id = entryQuery.value(QStringLiteral("id")).toString();
return getEntry(id);
}
return nullptr;
}
int DataManager::feedCount() const int DataManager::feedCount() const
{ {
return m_feedmap.count(); return m_feedmap.count();
@ -153,6 +175,24 @@ int DataManager::entryCount(const Feed* feed) const
return m_entrymap[feed->url()].count(); return m_entrymap[feed->url()].count();
} }
int DataManager::entryCount(const EpisodeModel::Type type) const
{
QSqlQuery query;
if (type == EpisodeModel::All || type == EpisodeModel::New) {
if (type == EpisodeModel::New) {
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries WHERE new=:new;"));
query.bindValue(QStringLiteral(":new"), true);
} else { // i.e. EpisodeModel::All
query.prepare(QStringLiteral("SELECT COUNT (id) FROM Entries;"));
}
Database::instance().execute(query);
if (!query.next())
return -1;
return query.value(0).toInt();
}
return -1;
}
int DataManager::unreadEntryCount(const Feed* feed) const int DataManager::unreadEntryCount(const Feed* feed) const
{ {
QSqlQuery query; QSqlQuery query;

View File

@ -8,6 +8,7 @@
#include "feed.h" #include "feed.h"
#include "entry.h" #include "entry.h"
#include "episodemodel.h"
class DataManager : public QObject class DataManager : public QObject
{ {
@ -24,10 +25,12 @@ public:
Feed* getFeed(QString const feedurl) const; Feed* getFeed(QString const feedurl) const;
Entry* getEntry(int const feed_index, int const entry_index) const; Entry* getEntry(int const feed_index, int const entry_index) const;
Entry* getEntry(const Feed* feed, int const entry_index) const; Entry* getEntry(const Feed* feed, int const entry_index) const;
Entry* getEntry(const EpisodeModel::Type type, const int entry_index) const;
Q_INVOKABLE Entry* getEntry(const QString id) const; Q_INVOKABLE Entry* getEntry(const QString id) const;
int feedCount() const; int feedCount() const;
int entryCount(const int feed_index) const; int entryCount(const int feed_index) const;
int entryCount(const Feed* feed) const; int entryCount(const Feed* feed) const;
int entryCount(const EpisodeModel::Type type) const;
int unreadEntryCount(const Feed* feed) const; int unreadEntryCount(const Feed* feed) const;
int newEntryCount(const Feed* feed) const; int newEntryCount(const Feed* feed) const;
Q_INVOKABLE void addFeed(const QString &url); Q_INVOKABLE void addFeed(const QString &url);
@ -66,6 +69,9 @@ Q_SIGNALS:
void queueEntryRemoved(const int &index, const QString &id); void queueEntryRemoved(const int &index, const QString &id);
void queueEntryMoved(const int &from, const int &to); void queueEntryMoved(const int &from, const int &to);
void unreadEntryCountChanged(const QString &url);
void newEntryCountChanged(const QString &url);
private: private:
DataManager(); DataManager();
void loadFeed(QString feedurl) const; void loadFeed(QString feedurl) const;

View File

@ -133,6 +133,8 @@ void Entry::setRead(const bool read)
query.bindValue(QStringLiteral(":read"), m_read); query.bindValue(QStringLiteral(":read"), m_read);
Database::instance().execute(query); Database::instance().execute(query);
Q_EMIT m_feed->unreadEntryCountChanged(); Q_EMIT m_feed->unreadEntryCountChanged();
Q_EMIT DataManager::instance().unreadEntryCountChanged(m_feed->url());
//TODO: can one of the two slots be removed??
} }
void Entry::setNew(const bool state) void Entry::setNew(const bool state)
@ -146,6 +148,7 @@ void Entry::setNew(const bool state)
query.bindValue(QStringLiteral(":new"), m_new); query.bindValue(QStringLiteral(":new"), m_new);
Database::instance().execute(query); Database::instance().execute(query);
// Q_EMIT m_feed->newEntryCountChanged(); // TODO: signal and slots to be implemented // Q_EMIT m_feed->newEntryCountChanged(); // TODO: signal and slots to be implemented
Q_EMIT DataManager::instance().newEntryCountChanged(m_feed->url());
} }
QString Entry::adjustedContent(int width, int fontSize) QString Entry::adjustedContent(int width, int fontSize)

61
src/episodemodel.cpp Normal file
View File

@ -0,0 +1,61 @@
/**
* SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "episodemodel.h"
#include "datamanager.h"
EpisodeModel::EpisodeModel()
: QAbstractListModel(nullptr)
{
// When feed is updated, the entire model needs to be reset because we
// cannot know where the new entries will be inserted into the list (or that
// maybe even items have been removed.
connect(&DataManager::instance(), &DataManager::feedEntriesUpdated, this, [this](const QString &url) {
// we have to reset the entire model in case entries are removed or added
// because we have no way of knowing where those entries will be added/removed
beginResetModel();
endResetModel();
});
}
QVariant EpisodeModel::data(const QModelIndex &index, int role) const
{
if (role != 0)
return QVariant();
return QVariant::fromValue(DataManager::instance().getEntry(m_type, index.row()));
}
QHash<int, QByteArray> EpisodeModel::roleNames() const
{
QHash<int, QByteArray> roleNames;
roleNames[0] = "entry";
return roleNames;
}
int EpisodeModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return DataManager::instance().entryCount(m_type);
}
EpisodeModel::Type EpisodeModel::type() const
{
return m_type;
}
void EpisodeModel::setType(EpisodeModel::Type type)
{
m_type = type;
if (m_type == EpisodeModel::New) {
connect(&DataManager::instance(), &DataManager::newEntryCountChanged, this, [this](const QString &url) {
// we have to reset the entire model in case entries are removed or added
// because we have no way of knowing where those entries will be added/removed
beginResetModel();
endResetModel();
});
}
}

41
src/episodemodel.h Normal file
View File

@ -0,0 +1,41 @@
/**
* SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#pragma once
#include <QAbstractListModel>
#include <QHash>
#include <QObject>
#include <QVariant>
class EpisodeModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Type {
All,
New,
Downloading,
Downloaded,
};
Q_ENUM(Type)
Q_PROPERTY(Type type READ type WRITE setType)
explicit EpisodeModel();
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent) const override;
Type type() const;
public Q_SLOTS:
void setType(Type type);
private:
Type m_type = Type::All;
};

View File

@ -29,6 +29,7 @@
#include "feedsmodel.h" #include "feedsmodel.h"
#include "fetcher.h" #include "fetcher.h"
#include "queuemodel.h" #include "queuemodel.h"
#include "episodemodel.h"
#include "datamanager.h" #include "datamanager.h"
#include "audiomanager.h" #include "audiomanager.h"
#include "mpris2/mpris2.h" #include "mpris2/mpris2.h"
@ -51,6 +52,7 @@ int main(int argc, char *argv[])
qmlRegisterType<FeedsModel>("org.kde.alligator", 1, 0, "FeedsModel"); qmlRegisterType<FeedsModel>("org.kde.alligator", 1, 0, "FeedsModel");
qmlRegisterType<QueueModel>("org.kde.alligator", 1, 0, "QueueModel"); qmlRegisterType<QueueModel>("org.kde.alligator", 1, 0, "QueueModel");
qmlRegisterType<EpisodeModel>("org.kde.alligator", 1, 0, "EpisodeModel");
qmlRegisterUncreatableType<EntriesModel>("org.kde.alligator", 1, 0, "EntriesModel", QStringLiteral("Get from Feed")); qmlRegisterUncreatableType<EntriesModel>("org.kde.alligator", 1, 0, "EntriesModel", QStringLiteral("Get from Feed"));
qmlRegisterUncreatableType<Enclosure>("org.kde.alligator", 1, 0, "Enclosure", QStringLiteral("Only for enums")); qmlRegisterUncreatableType<Enclosure>("org.kde.alligator", 1, 0, "Enclosure", QStringLiteral("Only for enums"));
qmlRegisterSingletonType<Fetcher>("org.kde.alligator", 1, 0, "Fetcher", [](QQmlEngine *engine, QJSEngine *) -> QObject * { qmlRegisterSingletonType<Fetcher>("org.kde.alligator", 1, 0, "Fetcher", [](QQmlEngine *engine, QJSEngine *) -> QObject * {

View File

@ -0,0 +1,63 @@
/**
* SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
* SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick 2.14
import QtQuick.Controls 2.14 as Controls
import QtQuick.Layouts 1.14
import QtGraphicalEffects 1.15
import QtMultimedia 5.15
import org.kde.kirigami 2.12 as Kirigami
import org.kde.alligator 1.0
Kirigami.ScrollablePage {
id: page
title: i18n("Episode List")
supportsRefreshing: true
onRefreshingChanged: {
if(refreshing) {
Fetcher.fetchAll()
refreshing = false
}
}
actions.main: Kirigami.Action {
text: i18n("Refresh all feeds")
iconName: "view-refresh"
onTriggered: refreshing = true
visible: !Kirigami.Settings.isMobile
}
Kirigami.PlaceholderMessage {
visible: episodeList.count === 0
width: Kirigami.Units.gridUnit * 20
anchors.centerIn: parent
text: i18n("No Entries available")
}
Component {
id: entryListDelegate
GenericEntryDelegate {
listView: episodeList
}
}
ListView {
id: episodeList
visible: count !== 0
model: EpisodeModel { type: EpisodeModel.All }
delegate: Kirigami.DelegateRecycler {
width: episodeList.width
sourceComponent: entryListDelegate
}
}
}

View File

@ -27,6 +27,7 @@ Kirigami.ApplicationWindow {
pageStack.initialPage: mainPagePool.loadPage(SettingsManager.lastOpenedPage === "FeedListPage" ? "qrc:/FeedListPage.qml" pageStack.initialPage: mainPagePool.loadPage(SettingsManager.lastOpenedPage === "FeedListPage" ? "qrc:/FeedListPage.qml"
: SettingsManager.lastOpenedPage === "QueuePage" ? "qrc:/QueuePage.qml" : SettingsManager.lastOpenedPage === "QueuePage" ? "qrc:/QueuePage.qml"
: SettingsManager.lastOpenedPage === "EpisodeListPage" ? "qrc:/EpisodeListPage.qml"
: "qrc:/FeedListPage.qml") : "qrc:/FeedListPage.qml")
globalDrawer: Kirigami.GlobalDrawer { globalDrawer: Kirigami.GlobalDrawer {
@ -45,9 +46,18 @@ Kirigami.ApplicationWindow {
} }
}, },
Kirigami.PagePoolAction { Kirigami.PagePoolAction {
text: i18n("Subscriptions") text: i18n("Episodes")
iconName: "rss" iconName: "rss"
pagePool: mainPagePool pagePool: mainPagePool
page: "qrc:/EpisodeListPage.qml"
onTriggered: {
SettingsManager.lastOpenedPage = "EpisodeListPage" // for persistency
}
},
Kirigami.PagePoolAction {
text: i18n("Subscriptions")
iconName: "document-open-folder"
pagePool: mainPagePool
page: "qrc:/FeedListPage.qml" page: "qrc:/FeedListPage.qml"
onTriggered: { onTriggered: {
SettingsManager.lastOpenedPage = "FeedListPage" // for persistency SettingsManager.lastOpenedPage = "FeedListPage" // for persistency

View File

@ -13,6 +13,7 @@
<file alias="PlayerControls.qml">qml/PlayerControls.qml</file> <file alias="PlayerControls.qml">qml/PlayerControls.qml</file>
<file alias="FooterBar.qml">qml/FooterBar.qml</file> <file alias="FooterBar.qml">qml/FooterBar.qml</file>
<file alias="QueuePage.qml">qml/QueuePage.qml</file> <file alias="QueuePage.qml">qml/QueuePage.qml</file>
<file alias="EpisodeListPage.qml">qml/EpisodeListPage.qml</file>
<file alias="GenericListHeader.qml">qml/GenericListHeader.qml</file> <file alias="GenericListHeader.qml">qml/GenericListHeader.qml</file>
<file alias="GenericEntryDelegate.qml">qml/GenericEntryDelegate.qml</file> <file alias="GenericEntryDelegate.qml">qml/GenericEntryDelegate.qml</file>
<file alias="logo.png">../logo.png</file> <file alias="logo.png">../logo.png</file>