From eeee3f0c042f7cf3945c08ce9d8c93e7a88a9fa0 Mon Sep 17 00:00:00 2001 From: Digital Artifex <7929434+DigitalArtifex@users.noreply.github.com> Date: Tue, 16 Jun 2026 23:53:03 -0400 Subject: [PATCH] Added featured endpoint models and paginators --- KomplexHubPlugin/CMakeLists.txt | 7 + KomplexHubPlugin/featuredimagesmodel.cpp | 254 +++++++++++++++ KomplexHubPlugin/featuredimagesmodel.h | 303 ++++++++++++++++++ KomplexHubPlugin/featuredpacksmodel.cpp | 254 +++++++++++++++ KomplexHubPlugin/featuredpacksmodel.h | 303 ++++++++++++++++++ KomplexHubPlugin/featuredvideosmodel.cpp | 254 +++++++++++++++ KomplexHubPlugin/featuredvideosmodel.h | 303 ++++++++++++++++++ .../paginators/featuredvideospaginator.h | 45 +++ 8 files changed, 1723 insertions(+) create mode 100644 KomplexHubPlugin/featuredimagesmodel.cpp create mode 100644 KomplexHubPlugin/featuredimagesmodel.h create mode 100644 KomplexHubPlugin/featuredpacksmodel.cpp create mode 100644 KomplexHubPlugin/featuredpacksmodel.h create mode 100644 KomplexHubPlugin/featuredvideosmodel.cpp create mode 100644 KomplexHubPlugin/featuredvideosmodel.h create mode 100644 KomplexHubPlugin/paginators/featuredvideospaginator.h diff --git a/KomplexHubPlugin/CMakeLists.txt b/KomplexHubPlugin/CMakeLists.txt index 882676b..6b71ef3 100644 --- a/KomplexHubPlugin/CMakeLists.txt +++ b/KomplexHubPlugin/CMakeLists.txt @@ -39,6 +39,13 @@ qt_add_qml_module( SOURCES paginators/searchshaderspaginator.h SOURCES paginators/searchcubemapspaginator.h SOURCES paginators/searchvideospaginator.h + SOURCES featuredpacksmodel.h + SOURCES featuredpacksmodel.cpp + SOURCES featuredimagesmodel.h + SOURCES featuredimagesmodel.cpp + SOURCES featuredvideosmodel.h + SOURCES featuredvideosmodel.cpp + SOURCES paginators/featuredvideospaginator.h ) diff --git a/KomplexHubPlugin/featuredimagesmodel.cpp b/KomplexHubPlugin/featuredimagesmodel.cpp new file mode 100644 index 0000000..1d3ea1c --- /dev/null +++ b/KomplexHubPlugin/featuredimagesmodel.cpp @@ -0,0 +1,254 @@ +#include "featuredimagesmodel.h" +#include "common/wallpapercache.h" + +FeaturedImagesModel::FeaturedImagesModel(QObject *parent) : QAbstractListModel{parent} +{ + m_paginator = new FeaturedImagesPaginator(this); + PaginationNotifier *notifier = static_cast(m_paginator); + + QObject::connect( + notifier, + &PaginationNotifier::resultsPerPageChanged, + this, + &FeaturedImagesModel::resultsPerPageChanged + ); + + QObject::connect( + notifier, + &PaginationNotifier::totalResultsChanged, + this, + &FeaturedImagesModel::totalResultsChanged + ); + + QObject::connect( + notifier, + &PaginationNotifier::totalPagesChanged, + this, + &FeaturedImagesModel::totalPagesChanged + ); + + QObject::connect( + notifier, + &PaginationNotifier::pageChanged, + this, + &FeaturedImagesModel::pageChanged + ); + + QObject::connect( + notifier, + &PaginationNotifier::dataChanged, + this, + &FeaturedImagesModel::resetDataModel + ); +} + +auto FeaturedImagesModel::rowCount(const QModelIndex &) const -> int +{ + if(m_paginator != nullptr) + { + return m_paginator->count(); + } + + return 0; +} + +auto FeaturedImagesModel::data(const QModelIndex &index, int role) const -> QVariant +{ + if(index.row() < 0 || m_paginator == nullptr) + { + return {}; + } + + WallpaperCache dataPoint = m_data.at(index.row()); + + QVariant data; + + switch(static_cast(role)) + { + case UuidRole: + data = dataPoint.uuid; + break; + case AuthorRole: + data = dataPoint.author; + break; + case DescriptionRole: + data = dataPoint.description; + break; + case NameRole: + data = dataPoint.name; + break; + case ThumbnailRole: + data = dataPoint.thumbnail; + break; + case CreatedDateRole: + data = dataPoint.createdDate; + break; + case AuthorIdRole: + data = dataPoint.authorId; + break; + case PriceRole: + data = dataPoint.price; + break; + case CurrencyRole: + data = dataPoint.currency; + break; + case DownloadCountRole: + data = dataPoint.downloadCount; + break; + case TypeRole: + data = dataPoint.type; + break; + } + + return data; +} + +auto FeaturedImagesModel::index(int row, int column, const QModelIndex &parent) const -> QModelIndex +{ + Q_UNUSED(parent) + + if(row < 0 || row >= m_data.count()) + return {}; + + return createIndex(row, column, &m_data[row]); +} + +auto FeaturedImagesModel::columnCount(const QModelIndex &) const -> int +{ + return 0; +} + +auto FeaturedImagesModel::parent(const QModelIndex &) const -> QModelIndex +{ + return {}; +} + +auto FeaturedImagesModel::setData(const QModelIndex &, const QVariant &, int) -> bool +{ + return false; +} + +auto FeaturedImagesModel::state() const -> FeaturedImagesModel::State +{ + return m_state; +} + +auto FeaturedImagesModel::setState(State state) -> void +{ + if (m_state == state) + { + return; + } + + m_state = state; + emit stateChanged(); +} + +auto FeaturedImagesModel::resetState() -> void +{ + setState(Idle); +} + +auto FeaturedImagesModel::resetDataModel() -> void +{ + //invalidate previous model data + beginRemoveRows(QModelIndex(), 0, m_data.count()); + m_data.clear(); + endRemoveRows(); + + //signal new data + beginInsertRows(QModelIndex(), 0, m_paginator->count() - 1); + auto dataset = m_paginator->data(); + + for(const WallpaperCache& data : std::as_const(dataset)) + { + m_data.append(data); + } + + endInsertRows(); +} + +auto FeaturedImagesModel::roleNames() const -> QHash +{ + return m_dataRoles; +} + +auto FeaturedImagesModel::errorString() const -> QString +{ + return m_errorString; +} + +auto FeaturedImagesModel::setErrorString(const QString &errorString) -> void +{ + if (m_errorString == errorString) + { + return; + } + + m_errorString = errorString; + emit errorStringChanged(); +} + +auto FeaturedImagesModel::resultsPerPage() const -> qsizetype +{ + if(m_paginator != nullptr) + { + return m_paginator->resultsPerPage(); + } + + return 0; +} + +auto FeaturedImagesModel::setResultsPerPage(qsizetype resultsPerPage) -> void +{ + if(m_paginator != nullptr) + { + m_paginator->setResultsPerPage(resultsPerPage); + } +} + +auto FeaturedImagesModel::totalResults() const -> qsizetype +{ + if(m_paginator != nullptr) + { + return m_paginator->totalResults(); + } + + return 0; +} + +auto FeaturedImagesModel::nextPage() const -> void +{ + if(m_paginator != nullptr) + { + m_paginator->next(); + } +} + +auto FeaturedImagesModel::previousPage() const -> void +{ + if(m_paginator != nullptr) + { + m_paginator->previous(); + } +} + +qsizetype FeaturedImagesModel::page() const +{ + if(m_paginator != nullptr) + { + return m_paginator->page(); + } + + return 0; +} + +auto FeaturedImagesModel::totalPages() const -> qsizetype +{ + if(m_paginator != nullptr) + { + return m_paginator->totalPages(); + } + + return 0; +} diff --git a/KomplexHubPlugin/featuredimagesmodel.h b/KomplexHubPlugin/featuredimagesmodel.h new file mode 100644 index 0000000..f96542b --- /dev/null +++ b/KomplexHubPlugin/featuredimagesmodel.h @@ -0,0 +1,303 @@ +/* + * Komplex Wallpaper Engine + * Copyright (C) 2026 @DigitalArtifex + * https://digitalartifex.dev - https://github.com/DigitalArtifex + * + * 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 3 of the License, or + * (at your option) any later version. + * + * 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 + */ +#ifndef FeaturedImagesModel_H +#define FeaturedImagesModel_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "paginators/featuredimagespaginator.h" + +/** + * @brief The FeaturedImagesModel class + */ +class KOMPLEX_EXPORT FeaturedImagesModel : public QAbstractListModel +{ + Q_OBJECT + QML_ELEMENT +public: + + /** + * @brief Used to control the UI state + */ + enum State + { + Idle, + Loading, + Error + }; + Q_ENUM(State) + + /** + * @brief Data roles used by the view to request data + */ + enum DataRole { + UuidRole = Qt::UserRole + 1, + AuthorRole, + AuthorIdRole, + DescriptionRole, + NameRole, + ThumbnailRole, + CreatedDateRole, + PriceRole, + CurrencyRole, + DownloadCountRole, + TypeRole + }; + Q_ENUM(DataRole) + + explicit FeaturedImagesModel(QObject *parent = nullptr); + + auto rowCount(const QModelIndex &parent = QModelIndex()) const -> int override; + + /** + * @brief data + * Used by the view to pull data from the model. + * Required by QAbstractListModel + * @param index + * @param role + * @return + */ + auto data( + const QModelIndex &index, + int role = Qt::DisplayRole + ) const -> QVariant override; + + /** + * @brief index + * Used by the view to create an index for the data point. + * Required by QAbstractListModel + * @param row + * @param column + * @param parent + * @return + */ + auto index( + int row, + int column, + const QModelIndex &parent = QModelIndex() + ) const -> QModelIndex override; + + /** + * @brief columnCount + * Required by QAbstractListModel, but just returns 0 + * since we dont use cols + * @param parent + * @return + */ + auto columnCount( + const QModelIndex &parent = QModelIndex() + ) const -> int override; + + /** + * @brief parent + * @param index + * @return + */ + auto parent(const QModelIndex &index) const -> QModelIndex override; + + /** + * @brief setData + * Required by QAbstractListModel but not used + * @param index + * @param value + * @param role + * @return true always + */ + auto setData( + const QModelIndex &index, + const QVariant &value, + int role = Qt::EditRole + ) -> bool override; + + /** + * @brief state + * Current Model State + * @return + */ + auto state() const -> State; + + /** + * @brief roleNames + * Used to tell the view what data roles are available + * and their QML friendly names + * @return + */ + auto roleNames() const -> QHash override; + + /** + * @brief errorString + * User-friendly error string to be reported to the user + * @return + */ + auto errorString() const -> QString; + + /** + * @brief resultsPerPage + * @return + */ + auto resultsPerPage() const -> qsizetype; + + /** + * @brief setResultsPerPage + * @param resultsPerPage + */ + auto setResultsPerPage(qsizetype resultsPerPage) -> void; + + /** + * @brief totalResults + * Total number of results available + * @return + */ + auto totalResults() const -> qsizetype; + + /** + * @brief page + * @return + */ + auto page() const -> qsizetype; + + /** + * @brief totalPages + * Total pages based on the current resultsPerPage + * @return + */ + auto totalPages() const -> qsizetype; + + /** + * @brief nextPage + * Function for the QML frontend + */ + Q_INVOKABLE auto nextPage() const -> void; + + /** + * @brief previousPage + * Function for the QML frontend + */ + Q_INVOKABLE auto previousPage() const -> void; + +protected: + /** + * @brief setErrorString + * @param errorString + */ + auto setErrorString(const QString &errorString) -> void; + + /** + * @brief setState + * @param state + */ + auto setState(State state) -> void; + + /** + * @brief resetState + */ + auto resetState() -> void; + + auto resetDataModel() -> void; + +signals: + auto stateChanged() -> void; + auto errorStringChanged() -> void; + auto resultsPerPageChanged() -> void; + + auto pageChanged() -> void; + auto totalResultsChanged() -> void; + auto totalPagesChanged() -> void; + +private: + static inline const QHash m_dataRoles = + { + { + static_cast(UuidRole), + QByteArray("uuid") + }, + { + static_cast(AuthorRole), + QByteArray("author") + }, + { + static_cast(AuthorIdRole), + QByteArray("authorId") + }, + { + static_cast(DescriptionRole), + QByteArray("description") + }, + { + static_cast(NameRole), + QByteArray("name") + }, + { + static_cast(ThumbnailRole), + QByteArray("thumbnail") + }, + { + static_cast(CreatedDateRole), + QByteArray("createdDate") + }, + { + static_cast(PriceRole), + QByteArray("price") + }, + { + static_cast(CurrencyRole), + QByteArray("currency") + }, + { + static_cast(DownloadCountRole), + QByteArray("downloadCount") + }, + { + static_cast(TypeRole), + QByteArray("type") + } + }; + State m_state = Idle; + + QString m_errorString = QString(); + + FeaturedImagesPaginator *m_paginator = nullptr; + + mutable QList m_data; + + Q_PROPERTY(State state READ state WRITE setState RESET resetState NOTIFY stateChanged FINAL) + Q_PROPERTY(QString errorString READ errorString WRITE setErrorString NOTIFY errorStringChanged FINAL) + Q_PROPERTY(qsizetype resultsPerPage READ resultsPerPage WRITE setResultsPerPage NOTIFY resultsPerPageChanged FINAL) + Q_PROPERTY(qsizetype totalResults READ totalResults NOTIFY totalResultsChanged FINAL) + Q_PROPERTY(qsizetype totalPages READ totalPages NOTIFY totalPagesChanged FINAL) + Q_PROPERTY(qsizetype page READ page NOTIFY pageChanged FINAL) +}; +Q_DECLARE_METATYPE(FeaturedImagesModel) + +#endif // FeaturedImagesModel_H diff --git a/KomplexHubPlugin/featuredpacksmodel.cpp b/KomplexHubPlugin/featuredpacksmodel.cpp new file mode 100644 index 0000000..56224bf --- /dev/null +++ b/KomplexHubPlugin/featuredpacksmodel.cpp @@ -0,0 +1,254 @@ +#include "featuredpacksmodel.h" +#include "common/wallpapercache.h" + +FeaturedPacksModel::FeaturedPacksModel(QObject *parent) : QAbstractListModel{parent} +{ + m_paginator = new FeaturedPacksPaginator(this); + PaginationNotifier *notifier = static_cast(m_paginator); + + QObject::connect( + notifier, + &PaginationNotifier::resultsPerPageChanged, + this, + &FeaturedPacksModel::resultsPerPageChanged + ); + + QObject::connect( + notifier, + &PaginationNotifier::totalResultsChanged, + this, + &FeaturedPacksModel::totalResultsChanged + ); + + QObject::connect( + notifier, + &PaginationNotifier::totalPagesChanged, + this, + &FeaturedPacksModel::totalPagesChanged + ); + + QObject::connect( + notifier, + &PaginationNotifier::pageChanged, + this, + &FeaturedPacksModel::pageChanged + ); + + QObject::connect( + notifier, + &PaginationNotifier::dataChanged, + this, + &FeaturedPacksModel::resetDataModel + ); +} + +auto FeaturedPacksModel::rowCount(const QModelIndex &) const -> int +{ + if(m_paginator != nullptr) + { + return m_paginator->count(); + } + + return 0; +} + +auto FeaturedPacksModel::data(const QModelIndex &index, int role) const -> QVariant +{ + if(index.row() < 0 || m_paginator == nullptr) + { + return {}; + } + + WallpaperCache dataPoint = m_data.at(index.row()); + + QVariant data; + + switch(static_cast(role)) + { + case UuidRole: + data = dataPoint.uuid; + break; + case AuthorRole: + data = dataPoint.author; + break; + case DescriptionRole: + data = dataPoint.description; + break; + case NameRole: + data = dataPoint.name; + break; + case ThumbnailRole: + data = dataPoint.thumbnail; + break; + case CreatedDateRole: + data = dataPoint.createdDate; + break; + case AuthorIdRole: + data = dataPoint.authorId; + break; + case PriceRole: + data = dataPoint.price; + break; + case CurrencyRole: + data = dataPoint.currency; + break; + case DownloadCountRole: + data = dataPoint.downloadCount; + break; + case TypeRole: + data = dataPoint.type; + break; + } + + return data; +} + +auto FeaturedPacksModel::index(int row, int column, const QModelIndex &parent) const -> QModelIndex +{ + Q_UNUSED(parent) + + if(row < 0 || row >= m_data.count()) + return {}; + + return createIndex(row, column, &m_data[row]); +} + +auto FeaturedPacksModel::columnCount(const QModelIndex &) const -> int +{ + return 0; +} + +auto FeaturedPacksModel::parent(const QModelIndex &) const -> QModelIndex +{ + return {}; +} + +auto FeaturedPacksModel::setData(const QModelIndex &, const QVariant &, int) -> bool +{ + return false; +} + +auto FeaturedPacksModel::state() const -> FeaturedPacksModel::State +{ + return m_state; +} + +auto FeaturedPacksModel::setState(State state) -> void +{ + if (m_state == state) + { + return; + } + + m_state = state; + emit stateChanged(); +} + +auto FeaturedPacksModel::resetState() -> void +{ + setState(Idle); +} + +auto FeaturedPacksModel::resetDataModel() -> void +{ + //invalidate previous model data + beginRemoveRows(QModelIndex(), 0, m_data.count()); + m_data.clear(); + endRemoveRows(); + + //signal new data + beginInsertRows(QModelIndex(), 0, m_paginator->count() - 1); + auto dataset = m_paginator->data(); + + for(const WallpaperCache& data : std::as_const(dataset)) + { + m_data.append(data); + } + + endInsertRows(); +} + +auto FeaturedPacksModel::roleNames() const -> QHash +{ + return m_dataRoles; +} + +auto FeaturedPacksModel::errorString() const -> QString +{ + return m_errorString; +} + +auto FeaturedPacksModel::setErrorString(const QString &errorString) -> void +{ + if (m_errorString == errorString) + { + return; + } + + m_errorString = errorString; + emit errorStringChanged(); +} + +auto FeaturedPacksModel::resultsPerPage() const -> qsizetype +{ + if(m_paginator != nullptr) + { + return m_paginator->resultsPerPage(); + } + + return 0; +} + +auto FeaturedPacksModel::setResultsPerPage(qsizetype resultsPerPage) -> void +{ + if(m_paginator != nullptr) + { + m_paginator->setResultsPerPage(resultsPerPage); + } +} + +auto FeaturedPacksModel::totalResults() const -> qsizetype +{ + if(m_paginator != nullptr) + { + return m_paginator->totalResults(); + } + + return 0; +} + +auto FeaturedPacksModel::nextPage() const -> void +{ + if(m_paginator != nullptr) + { + m_paginator->next(); + } +} + +auto FeaturedPacksModel::previousPage() const -> void +{ + if(m_paginator != nullptr) + { + m_paginator->previous(); + } +} + +qsizetype FeaturedPacksModel::page() const +{ + if(m_paginator != nullptr) + { + return m_paginator->page(); + } + + return 0; +} + +auto FeaturedPacksModel::totalPages() const -> qsizetype +{ + if(m_paginator != nullptr) + { + return m_paginator->totalPages(); + } + + return 0; +} diff --git a/KomplexHubPlugin/featuredpacksmodel.h b/KomplexHubPlugin/featuredpacksmodel.h new file mode 100644 index 0000000..2cb931c --- /dev/null +++ b/KomplexHubPlugin/featuredpacksmodel.h @@ -0,0 +1,303 @@ +/* + * Komplex Wallpaper Engine + * Copyright (C) 2026 @DigitalArtifex + * https://digitalartifex.dev - https://github.com/DigitalArtifex + * + * 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 3 of the License, or + * (at your option) any later version. + * + * 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 + */ +#ifndef FEATUREDPACKSMODEL_H +#define FEATUREDPACKSMODEL_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "paginators/featuredpackspaginator.h" + +/** + * @brief The FeaturedPacksModel class + */ +class KOMPLEX_EXPORT FeaturedPacksModel : public QAbstractListModel +{ + Q_OBJECT + QML_ELEMENT +public: + + /** + * @brief Used to control the UI state + */ + enum State + { + Idle, + Loading, + Error + }; + Q_ENUM(State) + + /** + * @brief Data roles used by the view to request data + */ + enum DataRole { + UuidRole = Qt::UserRole + 1, + AuthorRole, + AuthorIdRole, + DescriptionRole, + NameRole, + ThumbnailRole, + CreatedDateRole, + PriceRole, + CurrencyRole, + DownloadCountRole, + TypeRole + }; + Q_ENUM(DataRole) + + explicit FeaturedPacksModel(QObject *parent = nullptr); + + auto rowCount(const QModelIndex &parent = QModelIndex()) const -> int override; + + /** + * @brief data + * Used by the view to pull data from the model. + * Required by QAbstractListModel + * @param index + * @param role + * @return + */ + auto data( + const QModelIndex &index, + int role = Qt::DisplayRole + ) const -> QVariant override; + + /** + * @brief index + * Used by the view to create an index for the data point. + * Required by QAbstractListModel + * @param row + * @param column + * @param parent + * @return + */ + auto index( + int row, + int column, + const QModelIndex &parent = QModelIndex() + ) const -> QModelIndex override; + + /** + * @brief columnCount + * Required by QAbstractListModel, but just returns 0 + * since we dont use cols + * @param parent + * @return + */ + auto columnCount( + const QModelIndex &parent = QModelIndex() + ) const -> int override; + + /** + * @brief parent + * @param index + * @return + */ + auto parent(const QModelIndex &index) const -> QModelIndex override; + + /** + * @brief setData + * Required by QAbstractListModel but not used + * @param index + * @param value + * @param role + * @return true always + */ + auto setData( + const QModelIndex &index, + const QVariant &value, + int role = Qt::EditRole + ) -> bool override; + + /** + * @brief state + * Current Model State + * @return + */ + auto state() const -> State; + + /** + * @brief roleNames + * Used to tell the view what data roles are available + * and their QML friendly names + * @return + */ + auto roleNames() const -> QHash override; + + /** + * @brief errorString + * User-friendly error string to be reported to the user + * @return + */ + auto errorString() const -> QString; + + /** + * @brief resultsPerPage + * @return + */ + auto resultsPerPage() const -> qsizetype; + + /** + * @brief setResultsPerPage + * @param resultsPerPage + */ + auto setResultsPerPage(qsizetype resultsPerPage) -> void; + + /** + * @brief totalResults + * Total number of results available + * @return + */ + auto totalResults() const -> qsizetype; + + /** + * @brief page + * @return + */ + auto page() const -> qsizetype; + + /** + * @brief totalPages + * Total pages based on the current resultsPerPage + * @return + */ + auto totalPages() const -> qsizetype; + + /** + * @brief nextPage + * Function for the QML frontend + */ + Q_INVOKABLE auto nextPage() const -> void; + + /** + * @brief previousPage + * Function for the QML frontend + */ + Q_INVOKABLE auto previousPage() const -> void; + +protected: + /** + * @brief setErrorString + * @param errorString + */ + auto setErrorString(const QString &errorString) -> void; + + /** + * @brief setState + * @param state + */ + auto setState(State state) -> void; + + /** + * @brief resetState + */ + auto resetState() -> void; + + auto resetDataModel() -> void; + +signals: + auto stateChanged() -> void; + auto errorStringChanged() -> void; + auto resultsPerPageChanged() -> void; + + auto pageChanged() -> void; + auto totalResultsChanged() -> void; + auto totalPagesChanged() -> void; + +private: + static inline const QHash m_dataRoles = + { + { + static_cast(UuidRole), + QByteArray("uuid") + }, + { + static_cast(AuthorRole), + QByteArray("author") + }, + { + static_cast(AuthorIdRole), + QByteArray("authorId") + }, + { + static_cast(DescriptionRole), + QByteArray("description") + }, + { + static_cast(NameRole), + QByteArray("name") + }, + { + static_cast(ThumbnailRole), + QByteArray("thumbnail") + }, + { + static_cast(CreatedDateRole), + QByteArray("createdDate") + }, + { + static_cast(PriceRole), + QByteArray("price") + }, + { + static_cast(CurrencyRole), + QByteArray("currency") + }, + { + static_cast(DownloadCountRole), + QByteArray("downloadCount") + }, + { + static_cast(TypeRole), + QByteArray("type") + } + }; + State m_state = Idle; + + QString m_errorString = QString(); + + FeaturedPacksPaginator *m_paginator = nullptr; + + mutable QList m_data; + + Q_PROPERTY(State state READ state WRITE setState RESET resetState NOTIFY stateChanged FINAL) + Q_PROPERTY(QString errorString READ errorString WRITE setErrorString NOTIFY errorStringChanged FINAL) + Q_PROPERTY(qsizetype resultsPerPage READ resultsPerPage WRITE setResultsPerPage NOTIFY resultsPerPageChanged FINAL) + Q_PROPERTY(qsizetype totalResults READ totalResults NOTIFY totalResultsChanged FINAL) + Q_PROPERTY(qsizetype totalPages READ totalPages NOTIFY totalPagesChanged FINAL) + Q_PROPERTY(qsizetype page READ page NOTIFY pageChanged FINAL) +}; +Q_DECLARE_METATYPE(FeaturedPacksModel) + +#endif // FEATUREDPACKSMODEL_H diff --git a/KomplexHubPlugin/featuredvideosmodel.cpp b/KomplexHubPlugin/featuredvideosmodel.cpp new file mode 100644 index 0000000..2eeb4bc --- /dev/null +++ b/KomplexHubPlugin/featuredvideosmodel.cpp @@ -0,0 +1,254 @@ +#include "featuredvideosmodel.h" +#include "common/wallpapercache.h" + +FeaturedVideosModel::FeaturedVideosModel(QObject *parent) : QAbstractListModel{parent} +{ + m_paginator = new FeaturedVideosPaginator(this); + PaginationNotifier *notifier = static_cast(m_paginator); + + QObject::connect( + notifier, + &PaginationNotifier::resultsPerPageChanged, + this, + &FeaturedVideosModel::resultsPerPageChanged + ); + + QObject::connect( + notifier, + &PaginationNotifier::totalResultsChanged, + this, + &FeaturedVideosModel::totalResultsChanged + ); + + QObject::connect( + notifier, + &PaginationNotifier::totalPagesChanged, + this, + &FeaturedVideosModel::totalPagesChanged + ); + + QObject::connect( + notifier, + &PaginationNotifier::pageChanged, + this, + &FeaturedVideosModel::pageChanged + ); + + QObject::connect( + notifier, + &PaginationNotifier::dataChanged, + this, + &FeaturedVideosModel::resetDataModel + ); +} + +auto FeaturedVideosModel::rowCount(const QModelIndex &) const -> int +{ + if(m_paginator != nullptr) + { + return m_paginator->count(); + } + + return 0; +} + +auto FeaturedVideosModel::data(const QModelIndex &index, int role) const -> QVariant +{ + if(index.row() < 0 || m_paginator == nullptr) + { + return {}; + } + + WallpaperCache dataPoint = m_data.at(index.row()); + + QVariant data; + + switch(static_cast(role)) + { + case UuidRole: + data = dataPoint.uuid; + break; + case AuthorRole: + data = dataPoint.author; + break; + case DescriptionRole: + data = dataPoint.description; + break; + case NameRole: + data = dataPoint.name; + break; + case ThumbnailRole: + data = dataPoint.thumbnail; + break; + case CreatedDateRole: + data = dataPoint.createdDate; + break; + case AuthorIdRole: + data = dataPoint.authorId; + break; + case PriceRole: + data = dataPoint.price; + break; + case CurrencyRole: + data = dataPoint.currency; + break; + case DownloadCountRole: + data = dataPoint.downloadCount; + break; + case TypeRole: + data = dataPoint.type; + break; + } + + return data; +} + +auto FeaturedVideosModel::index(int row, int column, const QModelIndex &parent) const -> QModelIndex +{ + Q_UNUSED(parent) + + if(row < 0 || row >= m_data.count()) + return {}; + + return createIndex(row, column, &m_data[row]); +} + +auto FeaturedVideosModel::columnCount(const QModelIndex &) const -> int +{ + return 0; +} + +auto FeaturedVideosModel::parent(const QModelIndex &) const -> QModelIndex +{ + return {}; +} + +auto FeaturedVideosModel::setData(const QModelIndex &, const QVariant &, int) -> bool +{ + return false; +} + +auto FeaturedVideosModel::state() const -> FeaturedVideosModel::State +{ + return m_state; +} + +auto FeaturedVideosModel::setState(State state) -> void +{ + if (m_state == state) + { + return; + } + + m_state = state; + emit stateChanged(); +} + +auto FeaturedVideosModel::resetState() -> void +{ + setState(Idle); +} + +auto FeaturedVideosModel::resetDataModel() -> void +{ + //invalidate previous model data + beginRemoveRows(QModelIndex(), 0, m_data.count()); + m_data.clear(); + endRemoveRows(); + + //signal new data + beginInsertRows(QModelIndex(), 0, m_paginator->count() - 1); + auto dataset = m_paginator->data(); + + for(const WallpaperCache& data : std::as_const(dataset)) + { + m_data.append(data); + } + + endInsertRows(); +} + +auto FeaturedVideosModel::roleNames() const -> QHash +{ + return m_dataRoles; +} + +auto FeaturedVideosModel::errorString() const -> QString +{ + return m_errorString; +} + +auto FeaturedVideosModel::setErrorString(const QString &errorString) -> void +{ + if (m_errorString == errorString) + { + return; + } + + m_errorString = errorString; + emit errorStringChanged(); +} + +auto FeaturedVideosModel::resultsPerPage() const -> qsizetype +{ + if(m_paginator != nullptr) + { + return m_paginator->resultsPerPage(); + } + + return 0; +} + +auto FeaturedVideosModel::setResultsPerPage(qsizetype resultsPerPage) -> void +{ + if(m_paginator != nullptr) + { + m_paginator->setResultsPerPage(resultsPerPage); + } +} + +auto FeaturedVideosModel::totalResults() const -> qsizetype +{ + if(m_paginator != nullptr) + { + return m_paginator->totalResults(); + } + + return 0; +} + +auto FeaturedVideosModel::nextPage() const -> void +{ + if(m_paginator != nullptr) + { + m_paginator->next(); + } +} + +auto FeaturedVideosModel::previousPage() const -> void +{ + if(m_paginator != nullptr) + { + m_paginator->previous(); + } +} + +qsizetype FeaturedVideosModel::page() const +{ + if(m_paginator != nullptr) + { + return m_paginator->page(); + } + + return 0; +} + +auto FeaturedVideosModel::totalPages() const -> qsizetype +{ + if(m_paginator != nullptr) + { + return m_paginator->totalPages(); + } + + return 0; +} diff --git a/KomplexHubPlugin/featuredvideosmodel.h b/KomplexHubPlugin/featuredvideosmodel.h new file mode 100644 index 0000000..9b800ce --- /dev/null +++ b/KomplexHubPlugin/featuredvideosmodel.h @@ -0,0 +1,303 @@ +/* + * Komplex Wallpaper Engine + * Copyright (C) 2026 @DigitalArtifex + * https://digitalartifex.dev - https://github.com/DigitalArtifex + * + * 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 3 of the License, or + * (at your option) any later version. + * + * 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 + */ +#ifndef FEATUREDVIDEOSMODEL_H +#define FEATUREDVIDEOSMODEL_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "paginators/featuredvideospaginator.h" + +/** + * @brief The FeaturedVideosModel class + */ +class KOMPLEX_EXPORT FeaturedVideosModel : public QAbstractListModel +{ + Q_OBJECT + QML_ELEMENT +public: + + /** + * @brief Used to control the UI state + */ + enum State + { + Idle, + Loading, + Error + }; + Q_ENUM(State) + + /** + * @brief Data roles used by the view to request data + */ + enum DataRole { + UuidRole = Qt::UserRole + 1, + AuthorRole, + AuthorIdRole, + DescriptionRole, + NameRole, + ThumbnailRole, + CreatedDateRole, + PriceRole, + CurrencyRole, + DownloadCountRole, + TypeRole + }; + Q_ENUM(DataRole) + + explicit FeaturedVideosModel(QObject *parent = nullptr); + + auto rowCount(const QModelIndex &parent = QModelIndex()) const -> int override; + + /** + * @brief data + * Used by the view to pull data from the model. + * Required by QAbstractListModel + * @param index + * @param role + * @return + */ + auto data( + const QModelIndex &index, + int role = Qt::DisplayRole + ) const -> QVariant override; + + /** + * @brief index + * Used by the view to create an index for the data point. + * Required by QAbstractListModel + * @param row + * @param column + * @param parent + * @return + */ + auto index( + int row, + int column, + const QModelIndex &parent = QModelIndex() + ) const -> QModelIndex override; + + /** + * @brief columnCount + * Required by QAbstractListModel, but just returns 0 + * since we dont use cols + * @param parent + * @return + */ + auto columnCount( + const QModelIndex &parent = QModelIndex() + ) const -> int override; + + /** + * @brief parent + * @param index + * @return + */ + auto parent(const QModelIndex &index) const -> QModelIndex override; + + /** + * @brief setData + * Required by QAbstractListModel but not used + * @param index + * @param value + * @param role + * @return true always + */ + auto setData( + const QModelIndex &index, + const QVariant &value, + int role = Qt::EditRole + ) -> bool override; + + /** + * @brief state + * Current Model State + * @return + */ + auto state() const -> State; + + /** + * @brief roleNames + * Used to tell the view what data roles are available + * and their QML friendly names + * @return + */ + auto roleNames() const -> QHash override; + + /** + * @brief errorString + * User-friendly error string to be reported to the user + * @return + */ + auto errorString() const -> QString; + + /** + * @brief resultsPerPage + * @return + */ + auto resultsPerPage() const -> qsizetype; + + /** + * @brief setResultsPerPage + * @param resultsPerPage + */ + auto setResultsPerPage(qsizetype resultsPerPage) -> void; + + /** + * @brief totalResults + * Total number of results available + * @return + */ + auto totalResults() const -> qsizetype; + + /** + * @brief page + * @return + */ + auto page() const -> qsizetype; + + /** + * @brief totalPages + * Total pages based on the current resultsPerPage + * @return + */ + auto totalPages() const -> qsizetype; + + /** + * @brief nextPage + * Function for the QML frontend + */ + Q_INVOKABLE auto nextPage() const -> void; + + /** + * @brief previousPage + * Function for the QML frontend + */ + Q_INVOKABLE auto previousPage() const -> void; + +protected: + /** + * @brief setErrorString + * @param errorString + */ + auto setErrorString(const QString &errorString) -> void; + + /** + * @brief setState + * @param state + */ + auto setState(State state) -> void; + + /** + * @brief resetState + */ + auto resetState() -> void; + + auto resetDataModel() -> void; + +signals: + auto stateChanged() -> void; + auto errorStringChanged() -> void; + auto resultsPerPageChanged() -> void; + + auto pageChanged() -> void; + auto totalResultsChanged() -> void; + auto totalPagesChanged() -> void; + +private: + static inline const QHash m_dataRoles = + { + { + static_cast(UuidRole), + QByteArray("uuid") + }, + { + static_cast(AuthorRole), + QByteArray("author") + }, + { + static_cast(AuthorIdRole), + QByteArray("authorId") + }, + { + static_cast(DescriptionRole), + QByteArray("description") + }, + { + static_cast(NameRole), + QByteArray("name") + }, + { + static_cast(ThumbnailRole), + QByteArray("thumbnail") + }, + { + static_cast(CreatedDateRole), + QByteArray("createdDate") + }, + { + static_cast(PriceRole), + QByteArray("price") + }, + { + static_cast(CurrencyRole), + QByteArray("currency") + }, + { + static_cast(DownloadCountRole), + QByteArray("downloadCount") + }, + { + static_cast(TypeRole), + QByteArray("type") + } + }; + State m_state = Idle; + + QString m_errorString = QString(); + + FeaturedVideosPaginator *m_paginator = nullptr; + + mutable QList m_data; + + Q_PROPERTY(State state READ state WRITE setState RESET resetState NOTIFY stateChanged FINAL) + Q_PROPERTY(QString errorString READ errorString WRITE setErrorString NOTIFY errorStringChanged FINAL) + Q_PROPERTY(qsizetype resultsPerPage READ resultsPerPage WRITE setResultsPerPage NOTIFY resultsPerPageChanged FINAL) + Q_PROPERTY(qsizetype totalResults READ totalResults NOTIFY totalResultsChanged FINAL) + Q_PROPERTY(qsizetype totalPages READ totalPages NOTIFY totalPagesChanged FINAL) + Q_PROPERTY(qsizetype page READ page NOTIFY pageChanged FINAL) +}; +Q_DECLARE_METATYPE(FeaturedVideosModel) + +#endif // FEATUREDVIDEOSMODEL_H diff --git a/KomplexHubPlugin/paginators/featuredvideospaginator.h b/KomplexHubPlugin/paginators/featuredvideospaginator.h new file mode 100644 index 0000000..d25c658 --- /dev/null +++ b/KomplexHubPlugin/paginators/featuredvideospaginator.h @@ -0,0 +1,45 @@ +/* + * Komplex Wallpaper Engine + * Copyright (C) 2026 @DigitalArtifex + * https://digitalartifex.dev - https://github.com/DigitalArtifex + * + * 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 3 of the License, or + * (at your option) any later version. + * + * 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 + */ +#ifndef FEATUREDVIDEOSPAGINATOR_H +#define FEATUREDVIDEOSPAGINATOR_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wallpaperpaginator.h" + +class KOMPLEX_EXPORT FeaturedVideosPaginator : public WallpaperPaginator +{ +public: + explicit FeaturedVideosPaginator(QObject *parent = nullptr) : WallpaperPaginator(parent) + { + setUri( + QString::fromUtf8(KOMPLEX_ENDPOINT_VIDEOS_FEATURED) + ); + } +}; + +#endif // FEATUREDVIDEOSPAGINATOR_H