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
|
||||
enclosuredownloadjob.cpp
|
||||
queuemodel.cpp
|
||||
episodemodel.cpp
|
||||
datamanager.cpp
|
||||
audiomanager.cpp
|
||||
powermanagementinterface.cpp
|
||||
|
|
|
@ -138,6 +138,28 @@ Entry* DataManager::getEntry(QString id) const
|
|||
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
|
||||
{
|
||||
return m_feedmap.count();
|
||||
|
@ -153,6 +175,24 @@ int DataManager::entryCount(const Feed* feed) const
|
|||
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
|
||||
{
|
||||
QSqlQuery query;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "feed.h"
|
||||
#include "entry.h"
|
||||
#include "episodemodel.h"
|
||||
|
||||
class DataManager : public QObject
|
||||
{
|
||||
|
@ -24,10 +25,12 @@ public:
|
|||
Feed* getFeed(QString const feedurl) 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 EpisodeModel::Type type, const int entry_index) const;
|
||||
Q_INVOKABLE Entry* getEntry(const QString id) const;
|
||||
int feedCount() const;
|
||||
int entryCount(const int feed_index) const;
|
||||
int entryCount(const Feed* feed) const;
|
||||
int entryCount(const EpisodeModel::Type type) const;
|
||||
int unreadEntryCount(const Feed* feed) const;
|
||||
int newEntryCount(const Feed* feed) const;
|
||||
Q_INVOKABLE void addFeed(const QString &url);
|
||||
|
@ -66,6 +69,9 @@ Q_SIGNALS:
|
|||
void queueEntryRemoved(const int &index, const QString &id);
|
||||
void queueEntryMoved(const int &from, const int &to);
|
||||
|
||||
void unreadEntryCountChanged(const QString &url);
|
||||
void newEntryCountChanged(const QString &url);
|
||||
|
||||
private:
|
||||
DataManager();
|
||||
void loadFeed(QString feedurl) const;
|
||||
|
|
|
@ -133,6 +133,8 @@ void Entry::setRead(const bool read)
|
|||
query.bindValue(QStringLiteral(":read"), m_read);
|
||||
Database::instance().execute(query);
|
||||
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)
|
||||
|
@ -146,6 +148,7 @@ void Entry::setNew(const bool state)
|
|||
query.bindValue(QStringLiteral(":new"), m_new);
|
||||
Database::instance().execute(query);
|
||||
// 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)
|
||||
|
|
|
@ -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 "fetcher.h"
|
||||
#include "queuemodel.h"
|
||||
#include "episodemodel.h"
|
||||
#include "datamanager.h"
|
||||
#include "audiomanager.h"
|
||||
#include "mpris2/mpris2.h"
|
||||
|
@ -51,6 +52,7 @@ int main(int argc, char *argv[])
|
|||
|
||||
qmlRegisterType<FeedsModel>("org.kde.alligator", 1, 0, "FeedsModel");
|
||||
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<Enclosure>("org.kde.alligator", 1, 0, "Enclosure", QStringLiteral("Only for enums"));
|
||||
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"
|
||||
: SettingsManager.lastOpenedPage === "QueuePage" ? "qrc:/QueuePage.qml"
|
||||
: SettingsManager.lastOpenedPage === "EpisodeListPage" ? "qrc:/EpisodeListPage.qml"
|
||||
: "qrc:/FeedListPage.qml")
|
||||
|
||||
globalDrawer: Kirigami.GlobalDrawer {
|
||||
|
@ -45,9 +46,18 @@ Kirigami.ApplicationWindow {
|
|||
}
|
||||
},
|
||||
Kirigami.PagePoolAction {
|
||||
text: i18n("Subscriptions")
|
||||
text: i18n("Episodes")
|
||||
iconName: "rss"
|
||||
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"
|
||||
onTriggered: {
|
||||
SettingsManager.lastOpenedPage = "FeedListPage" // for persistency
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
<file alias="PlayerControls.qml">qml/PlayerControls.qml</file>
|
||||
<file alias="FooterBar.qml">qml/FooterBar.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="GenericEntryDelegate.qml">qml/GenericEntryDelegate.qml</file>
|
||||
<file alias="logo.png">../logo.png</file>
|
||||
|
|
Loading…
Reference in New Issue