mirror of https://github.com/KDE/kasts.git
Add FeedListPage sorting
The feeds will be sorted by (1) descending number of unread/unplayed entries and (2) alphabetically by name. The current item and current selection are maintained after re-sorting when the number of unread entries has changed.
This commit is contained in:
parent
c84d8ed47f
commit
bd1cf2c5f0
|
@ -17,6 +17,7 @@ set(SRCS_base
|
|||
storagemovejob.cpp
|
||||
models/chaptermodel.cpp
|
||||
models/feedsmodel.cpp
|
||||
models/feedsproxymodel.cpp
|
||||
models/entriesmodel.cpp
|
||||
models/queuemodel.cpp
|
||||
models/episodemodel.cpp
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
#include "models/episodemodel.h"
|
||||
#include "models/episodeproxymodel.h"
|
||||
#include "models/errorlogmodel.h"
|
||||
#include "models/feedsmodel.h"
|
||||
#include "models/feedsproxymodel.h"
|
||||
#include "models/podcastsearchmodel.h"
|
||||
#include "models/queuemodel.h"
|
||||
#include "mpris2/mpris2.h"
|
||||
|
@ -120,7 +120,7 @@ int main(int argc, char *argv[])
|
|||
|
||||
engine.rootContext()->setContextProperty(QStringLiteral("_aboutData"), QVariant::fromValue(about));
|
||||
|
||||
qmlRegisterType<FeedsModel>("org.kde.kasts", 1, 0, "FeedsModel");
|
||||
qmlRegisterType<FeedsProxyModel>("org.kde.kasts", 1, 0, "FeedsProxyModel");
|
||||
qmlRegisterType<QueueModel>("org.kde.kasts", 1, 0, "QueueModel");
|
||||
qmlRegisterType<EpisodeProxyModel>("org.kde.kasts", 1, 0, "EpisodeProxyModel");
|
||||
qmlRegisterType<Mpris2>("org.kde.kasts", 1, 0, "Mpris2");
|
||||
|
@ -130,6 +130,7 @@ int main(int argc, char *argv[])
|
|||
qmlRegisterUncreatableType<EntriesModel>("org.kde.kasts", 1, 0, "EntriesModel", QStringLiteral("Get from Feed"));
|
||||
qmlRegisterUncreatableType<Enclosure>("org.kde.kasts", 1, 0, "Enclosure", QStringLiteral("Only for enums"));
|
||||
qmlRegisterUncreatableType<EpisodeModel>("org.kde.kasts", 1, 0, "EpisodeModel", QStringLiteral("Only for enums"));
|
||||
qmlRegisterUncreatableType<FeedsModel>("org.kde.kasts", 1, 0, "FeedsModel", QStringLiteral("Only for enums"));
|
||||
|
||||
qmlRegisterSingletonInstance("org.kde.kasts", 1, 0, "Fetcher", &Fetcher::instance());
|
||||
qmlRegisterSingletonInstance("org.kde.kasts", 1, 0, "Database", &Database::instance());
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
#include "datamanager.h"
|
||||
#include "entry.h"
|
||||
|
||||
EpisodeModel::EpisodeModel()
|
||||
: QAbstractListModel(nullptr)
|
||||
EpisodeModel::EpisodeModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
// 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
|
||||
|
|
|
@ -25,7 +25,7 @@ public:
|
|||
};
|
||||
Q_ENUM(Roles)
|
||||
|
||||
explicit EpisodeModel();
|
||||
explicit EpisodeModel(QObject *parent = nullptr);
|
||||
QVariant data(const QModelIndex &index, int role = Qt::UserRole) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
|
|
|
@ -11,19 +11,14 @@
|
|||
#include "datamanager.h"
|
||||
#include "entry.h"
|
||||
|
||||
EpisodeProxyModel::EpisodeProxyModel()
|
||||
: QSortFilterProxyModel(nullptr)
|
||||
EpisodeProxyModel::EpisodeProxyModel(QObject *parent)
|
||||
: QSortFilterProxyModel(parent)
|
||||
{
|
||||
m_currentFilter = NoFilter;
|
||||
m_episodeModel = new EpisodeModel();
|
||||
m_episodeModel = new EpisodeModel(this);
|
||||
setSourceModel(m_episodeModel);
|
||||
}
|
||||
|
||||
EpisodeProxyModel::~EpisodeProxyModel()
|
||||
{
|
||||
delete m_episodeModel;
|
||||
}
|
||||
|
||||
bool EpisodeProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
|
|
|
@ -30,8 +30,7 @@ public:
|
|||
Q_PROPERTY(FilterType filterType READ filterType WRITE setFilterType NOTIFY filterTypeChanged)
|
||||
Q_PROPERTY(QString filterName READ filterName NOTIFY filterTypeChanged)
|
||||
|
||||
explicit EpisodeProxyModel();
|
||||
~EpisodeProxyModel();
|
||||
explicit EpisodeProxyModel(QObject *parent = nullptr);
|
||||
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
|
||||
|
|
|
@ -28,6 +28,14 @@ FeedsModel::FeedsModel(QObject *parent)
|
|||
beginRemoveRows(QModelIndex(), index, index);
|
||||
endRemoveRows();
|
||||
});
|
||||
connect(&DataManager::instance(), &DataManager::unreadEntryCountChanged, this, [=](const QString &url) {
|
||||
for (int i = 0; i < rowCount(QModelIndex()); i++) {
|
||||
if (data(index(i, 0), UrlRole).toString() == url) {
|
||||
Q_EMIT dataChanged(index(i, 0), index(i, 0));
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> FeedsModel::roleNames() const
|
||||
|
@ -61,9 +69,3 @@ QVariant FeedsModel::data(const QModelIndex &index, int role) const
|
|||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
// Hack to get a QItemSelection in QML
|
||||
QItemSelection FeedsModel::createSelection(int rowa, int rowb)
|
||||
{
|
||||
return QItemSelection(index(rowa, 0), index(rowb, 0));
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
#include <QAbstractListModel>
|
||||
#include <QHash>
|
||||
#include <QItemSelection>
|
||||
#include <QSqlTableModel>
|
||||
#include <QUrl>
|
||||
|
||||
|
@ -32,6 +31,4 @@ public:
|
|||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
|
||||
Q_INVOKABLE QItemSelection createSelection(int rowa, int rowb);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* 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 "models/feedsproxymodel.h"
|
||||
|
||||
FeedsProxyModel::FeedsProxyModel(QObject *parent)
|
||||
: QSortFilterProxyModel(parent)
|
||||
{
|
||||
m_feedsModel = new FeedsModel(this);
|
||||
setSourceModel(m_feedsModel);
|
||||
setDynamicSortFilter(true);
|
||||
sort(0, Qt::AscendingOrder);
|
||||
}
|
||||
|
||||
bool FeedsProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||
{
|
||||
QString leftTitle = sourceModel()->data(left, FeedsModel::TitleRole).toString();
|
||||
QString rightTitle = sourceModel()->data(right, FeedsModel::TitleRole).toString();
|
||||
int leftUnreadCount = sourceModel()->data(left, FeedsModel::UnreadCountRole).toInt();
|
||||
int rightUnreadCount = sourceModel()->data(right, FeedsModel::UnreadCountRole).toInt();
|
||||
|
||||
if (leftUnreadCount == rightUnreadCount) {
|
||||
return QString::localeAwareCompare(leftTitle, rightTitle) < 0;
|
||||
} else {
|
||||
return leftUnreadCount > rightUnreadCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Hack to get a QItemSelection in QML
|
||||
QItemSelection FeedsProxyModel::createSelection(int rowa, int rowb)
|
||||
{
|
||||
return QItemSelection(index(rowa, 0), index(rowb, 0));
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* 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 <QItemSelection>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "models/feedsmodel.h"
|
||||
|
||||
class Entry;
|
||||
|
||||
class FeedsProxyModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FeedsProxyModel(QObject *parent = nullptr);
|
||||
|
||||
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
|
||||
|
||||
Q_INVOKABLE QItemSelection createSelection(int rowa, int rowb);
|
||||
|
||||
private:
|
||||
FeedsModel *m_feedsModel;
|
||||
};
|
|
@ -18,14 +18,16 @@ import org.kde.kasts 1.0
|
|||
Controls.ItemDelegate {
|
||||
id: feedDelegate
|
||||
|
||||
required property int cardSize
|
||||
required property int cardMargin
|
||||
property int cardSize: 0
|
||||
property int cardMargin: 0
|
||||
property int borderWidth: 1
|
||||
implicitWidth: cardSize + 2 * cardMargin
|
||||
implicitHeight: cardSize + 2 * cardMargin
|
||||
|
||||
property QtObject listView: undefined
|
||||
property bool selected: false
|
||||
property bool isCurrentItem: false // to restore currentItem when model is resorted
|
||||
property string currentItemUrl: ""
|
||||
property int row: model ? model.row : -1
|
||||
property var activeBackgroundColor: Qt.lighter(Kirigami.Theme.highlightColor, 1.3)
|
||||
highlighted: selected
|
||||
|
@ -135,8 +137,26 @@ Controls.ItemDelegate {
|
|||
|
||||
Connections {
|
||||
target: listView.model
|
||||
function onLayoutAboutToBeChanged() {
|
||||
if (feedList.currentItem === feedDelegate) {
|
||||
isCurrentItem = true;
|
||||
currentItemUrl = feed.url;
|
||||
} else {
|
||||
isCurrentItem = false;
|
||||
currentItemUrl = "";
|
||||
}
|
||||
}
|
||||
function onLayoutChanged() {
|
||||
updateIsSelected();
|
||||
if (isCurrentItem) {
|
||||
// yet another hack because "index" is still giving the old
|
||||
// value here; so we have to manually find the new index.
|
||||
for (var i = 0; i < feedList.model.rowCount(); i++) {
|
||||
if (feedList.model.data(feedList.model.index(i, 0), FeedsModel.UrlRole) == currentItemUrl) {
|
||||
feedList.currentIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -115,21 +115,14 @@ Kirigami.ScrollablePage {
|
|||
cellWidth: availableWidth / columns
|
||||
cellHeight: availableWidth / columns
|
||||
|
||||
model: FeedsModel {
|
||||
model: FeedsProxyModel {
|
||||
id: feedsModel
|
||||
}
|
||||
|
||||
Component {
|
||||
id: feedListDelegate
|
||||
FeedListDelegate {
|
||||
cardSize: feedList.availableWidth / feedList.columns - 2 * feedList.cardMargin
|
||||
cardMargin: feedList.cardMargin
|
||||
listView: feedList
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Kirigami.DelegateRecycler {
|
||||
sourceComponent: feedListDelegate
|
||||
delegate: FeedListDelegate {
|
||||
cardSize: feedList.availableWidth / feedList.columns - 2 * feedList.cardMargin
|
||||
cardMargin: feedList.cardMargin
|
||||
listView: feedList
|
||||
}
|
||||
|
||||
property var selectionForContextMenu: []
|
||||
|
@ -153,6 +146,7 @@ Kirigami.ScrollablePage {
|
|||
currentIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
if (event.matches(StandardKey.SelectAll)) {
|
||||
feedList.selectionModel.select(model.index(0, 0), ItemSelectionModel.ClearAndSelect | ItemSelectionModel.Columns);
|
||||
|
|
Loading…
Reference in New Issue