mirror of https://github.com/KDE/kasts.git
Add first version of EpisodeListPage
This commit is contained in:
parent
62440e8609
commit
130b10aefb
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
|
@ -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 * {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue