Compare commits

...

6 Commits

Author SHA1 Message Date
Digital Artifex bf1903ddcd Added ImageSearch model and QML page 2026-06-18 01:11:16 -04:00
Digital Artifex 25a51257c1 Added missing trigger for button press 2026-06-18 01:10:08 -04:00
Digital Artifex 6a4dbced85 Fixed bug in reset logic 2026-06-18 01:05:10 -04:00
Digital Artifex 32085a6a52 Renamed SearchImagePaginator 2026-06-17 20:08:28 -04:00
Digital Artifex 7871f3a8cc Added missing calls to setState 2026-06-17 19:51:10 -04:00
Digital Artifex 0062043427 Added KeroLoadingAnimation 2026-06-17 19:50:18 -04:00
13 changed files with 872 additions and 30 deletions
+49 -4
View File
@@ -10,6 +10,7 @@ import QtQuick.Layouts
import KomplexHub import KomplexHub
import KomplexHub.Controls import KomplexHub.Controls
import KomplexHub.Kero import KomplexHub.Kero
import KomplexHubContent
Rectangle { Rectangle {
property MenuButton currentMenuButton property MenuButton currentMenuButton
@@ -78,6 +79,7 @@ Rectangle {
currentMenuButton = this currentMenuButton = this
searchContainer.preferredHeight = 50 searchContainer.preferredHeight = 50
// searchContainer.searchTerm = (pageLoader.page as ImageSearchPage).query
} }
} }
@@ -99,6 +101,7 @@ Rectangle {
currentMenuButton = this currentMenuButton = this
searchContainer.preferredHeight = 50 searchContainer.preferredHeight = 50
// searchContainer.searchTerm = pageLoader.page.query
} }
} }
@@ -176,6 +179,10 @@ Rectangle {
icon: "qrc:/images/icons/icons8-search.svg" icon: "qrc:/images/icons/icons8-search.svg"
onSearchTermChanged: () => {
pageLoader.setSearchTerm()
}
Behavior on preferredHeight { Behavior on preferredHeight {
NumberAnimation { duration: Constants.normalAnimationDuration } NumberAnimation { duration: Constants.normalAnimationDuration }
} }
@@ -212,36 +219,74 @@ Rectangle {
onFinished: () => { onFinished: () => {
if(pageLoader.loading && pageLoader.opacity === 0) if(pageLoader.loading && pageLoader.opacity === 0)
{
pageLoader.loadPage() pageLoader.loadPage()
}
else if(pageLoader.loading && pageLoader.opacity === 1) else if(pageLoader.loading && pageLoader.opacity === 1)
{
pageLoader.loading = false pageLoader.loading = false
pageLoader.updateSearchTerm();
}
} }
} }
function fadeOutPage() { function fadeOutPage()
{
pageLoaderAnimation.from = 1 pageLoaderAnimation.from = 1
pageLoaderAnimation.to = 0 pageLoaderAnimation.to = 0
pageLoaderAnimation.start() pageLoaderAnimation.start()
} }
function fadeInPage() { function fadeInPage()
{
if(pageLoader.sourceComponent.status != Component.Ready) if(pageLoader.sourceComponent.status != Component.Ready)
{
return return
}
pageLoaderAnimation.from = 0 pageLoaderAnimation.from = 0
pageLoaderAnimation.to = 1 pageLoaderAnimation.to = 1
pageLoaderAnimation.start() pageLoaderAnimation.start()
} }
function loadPage() { function loadPage()
{
sourceComponent = Qt.createComponent(page) sourceComponent = Qt.createComponent(page)
if(pageLoader.sourceComponent.status == Component.Ready) if(sourceComponent.status == Component.Ready)
{
fadeInPage() fadeInPage()
}
else else
{
statusChanged.connect(fadeInPage) statusChanged.connect(fadeInPage)
} }
} }
function updateSearchTerm()
{
if(isSearchPage())
{
searchContainer.searchTerm = pageLoader.item.query
}
}
function setSearchTerm()
{
if(isSearchPage())
{
pageLoader.item.query = searchContainer.searchTerm
}
}
function isSearchPage()
{
return (
pageLoader.item instanceof ImageSearchPage ||
pageLoader.item instanceof LiveSearchPage
);
}
}
} }
} }
} }
+37 -3
View File
@@ -48,7 +48,8 @@ Item {
Text { Text {
color: palette.text color: palette.text
font.pixelSize: Constants.largeFont.pixelSize font.pixelSize: Constants.h3Font.pixelSize
font.bold: true
text: "Newest Wallpaper Packs" text: "Newest Wallpaper Packs"
} }
@@ -57,6 +58,7 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 256 Layout.preferredHeight: 256
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.leftMargin: Constants.largeMargin
Repeater { Repeater {
model: newestPacksModel model: newestPacksModel
@@ -104,7 +106,8 @@ Item {
Text { Text {
color: palette.text color: palette.text
font.pixelSize: Constants.largeFont.pixelSize font.pixelSize: Constants.h3Font.pixelSize
font.bold: true
text: "Featured Images" text: "Featured Images"
} }
@@ -113,6 +116,7 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 256 Layout.preferredHeight: 256
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.leftMargin: Constants.largeMargin
Repeater { Repeater {
model: featuredImagesModel model: featuredImagesModel
@@ -159,7 +163,8 @@ Item {
Text { Text {
color: palette.text color: palette.text
font.pixelSize: Constants.largeFont.pixelSize font.pixelSize: Constants.h3Font.pixelSize
font.bold: true
text: "Featured Videos" text: "Featured Videos"
} }
@@ -168,6 +173,7 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 256 Layout.preferredHeight: 256
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.leftMargin: Constants.largeMargin
Repeater { Repeater {
model: featuredVideosModel model: featuredVideosModel
@@ -206,4 +212,32 @@ Item {
} }
} }
} }
KeroLoadingAnimation
{
id: loadingAnimation
anchors.fill: parent
OpacityAnimator on opacity {
duration: 150
}
visible: opacity > 0
opacity: 0
}
states: [
State {
name: "loading"
when: newestPacksModel.state == NewestPacksModel.Loading ||
featuredImagesModel.state == FeaturedImagesModel.Loading ||
featuredVideosModel.state == FeaturedVideosModel.Loading;
PropertyChanges {
target: loadingAnimation
opacity: 1
}
}
]
} }
+115 -12
View File
@@ -1,16 +1,109 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import KomplexHub import KomplexHub
import KomplexHub.Controls import KomplexHub.Controls
import KomplexHub.Kero import KomplexHub.Kero
import KomplexHubPlugin
Item
{
property string query
readonly property bool searchable: true
property int resultsPerRow: (resultsView.width - (64 + Constants.largeMargin)) / (256 + Constants.largeMargin)
property int rows: (resultsView.height - (Constants.largeMargin * 2) / (256 + Constants.largeMargin))
property int resultsPerPage: resultsPerRow * rows
Item {
id: imageSearchPageRoot id: imageSearchPageRoot
SearchResultList { ImageSearchModel
id: searchResultList {
id: searchModel
query: imageSearchPageRoot.query
resultsPerPage: imageSearchPageRoot.resultsPerPage
}
Rectangle
{
anchors.fill: parent anchors.fill: parent
color: palette.base color: palette.base
ScrollView
{
id: resultsView
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: buttonBoxLayout.top
GridLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.top: parent.top
anchors.margins: Constants.largeMargin
columns: imageSearchPageRoot.resultsPerRow
Repeater {
model: searchModel
delegate: Item {
width: 256
height: 256
required property string author
required property string description
required property string uuid
required property string thumbnail
required property string authorId
SearchResultItem
{
anchors.fill: parent
title: parent.author
author: qsTr("Pexels Images")
description: parent.description
thumbnail: parent.thumbnail
uuid: parent.uuid
}
}
}
}
}
RowLayout {
id: buttonBoxLayout
height: 50
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
SquareButton {
Layout.fillHeight: true
Layout.preferredWidth: 128
icon.source: "qrc:/images/icons/icons8-back.svg"
icon.height: 16
icon.width: 16
text: qsTr("Previous")
enabled: searchModel.hasPreviousPage
}
Item { Layout.fillWidth: true }
SquareButton {
Layout.fillHeight: true
Layout.preferredWidth: 128
icon.source: "qrc:/images/icons/icons8-next.svg"
icon.height: 16
icon.width: 16
text: qsTr("Next")
enabled: searchModel.hasNextPage
}
}
} }
KeroErrorAvatar { KeroErrorAvatar {
@@ -37,39 +130,49 @@ Item {
states: [ states: [
State { State {
name: "loading" name: "loading"
when: searchResultList.state === "loading" when: searchModel.state == ImageSearchModel.Loading
PropertyChanges { PropertyChanges
{
target: loadingOverlay target: loadingOverlay
opacity: 1 opacity: 1
} }
PropertyChanges { PropertyChanges
{
target: resultsView target: resultsView
opacity: 0 opacity: 0
} }
}, },
State { State {
name: "empty" name: "empty"
when: searchResultList.state === "empty" when: searchModel.totalResults === 0
PropertyChanges { PropertyChanges
{
target: noResultsOverlay target: noResultsOverlay
opacity: 1 opacity: 1
} }
PropertyChanges { PropertyChanges
{
target: resultsView target: resultsView
opacity: 0 opacity: 0
} }
PropertyChanges {
target: buttonBoxLayout
visible: false
}
}, },
State { State {
name: "error" name: "error"
when: searchResultList.state === "error" when: searchModel.state == ImageSearchModel.Error
PropertyChanges { PropertyChanges
{
target: errorOverlay target: errorOverlay
opacity: 1 opacity: 1
} }
PropertyChanges { PropertyChanges
{
target: resultsView target: resultsView
opacity: 0 opacity: 0
} }
+2
View File
@@ -52,6 +52,8 @@ Item {
icon.source: searchBarRootItem.icon icon.source: searchBarRootItem.icon
icon.width: 16 icon.width: 16
icon.height: 16 icon.height: 16
onTriggered: () => searchBarRootItem.searchTerm = searchEdit.text
} }
} }
} }
+3 -1
View File
@@ -31,7 +31,7 @@ qt_add_qml_module(
SOURCES paginators/featuredimagespaginator.h SOURCES paginators/featuredimagespaginator.h
SOURCES paginators/wallpaperpaginator.h SOURCES paginators/wallpaperpaginator.h
SOURCES paginators/searchpackspaginator.h SOURCES paginators/searchpackspaginator.h
SOURCES paginators/searchimagespaginator.h SOURCES paginators/imagesearchpaginator.h
SOURCES paginators/newestshaderspaginator.h SOURCES paginators/newestshaderspaginator.h
SOURCES paginators/newestcubemapspaginator.h SOURCES paginators/newestcubemapspaginator.h
SOURCES paginators/featuredshaderspaginator.h SOURCES paginators/featuredshaderspaginator.h
@@ -50,6 +50,8 @@ qt_add_qml_module(
SOURCES paginators/imagepaginator.h SOURCES paginators/imagepaginator.h
SOURCES common/videocache.h SOURCES common/videocache.h
SOURCES paginators/videopaginator.h SOURCES paginators/videopaginator.h
SOURCES imagesearchmodel.cpp
SOURCES imagesearchmodel.h
) )
+9 -2
View File
@@ -22,6 +22,7 @@
#include <QObject> #include <QObject>
#include <QMutex> #include <QMutex>
#include <QMutexLocker> #include <QMutexLocker>
#include <qdebug.h>
#include "komplex_global.h" #include "komplex_global.h"
#include "slidingcachecontroller.h" #include "slidingcachecontroller.h"
@@ -197,11 +198,16 @@ protected:
*/ */
auto setOffset(qsizetype offset) -> void auto setOffset(qsizetype offset) -> void
{ {
if(offset == m_offset) if(offset == m_offset && offset != std::numeric_limits<qsizetype>::infinity())
{ {
return; return;
} }
if(offset == std::numeric_limits<qsizetype>::infinity())
{
offset = 0;
}
m_offset = offset; m_offset = offset;
if(m_offset >= totalResults()) if(m_offset >= totalResults())
@@ -253,7 +259,6 @@ protected:
{ {
m_dataMutex.lock(); m_dataMutex.lock();
m_offset = std::numeric_limits<qsizetype>::infinity();
m_data.clear(); m_data.clear();
controller()->reset(); controller()->reset();
m_dataMutex.unlock(); m_dataMutex.unlock();
@@ -261,6 +266,8 @@ protected:
Q_EMIT totalPagesChanged(); Q_EMIT totalPagesChanged();
Q_EMIT dataChanged(); Q_EMIT dataChanged();
Q_EMIT pageChanged(); Q_EMIT pageChanged();
setOffset(std::numeric_limits<qsizetype>::infinity());
} }
private: private:
+6 -1
View File
@@ -1,8 +1,9 @@
#include "featuredimagesmodel.h" #include "featuredimagesmodel.h"
#include "common/wallpapercache.h"
FeaturedImagesModel::FeaturedImagesModel(QObject *parent) : QAbstractListModel{parent} FeaturedImagesModel::FeaturedImagesModel(QObject *parent) : QAbstractListModel{parent}
{ {
setState(Loading);
m_paginator = new FeaturedImagesPaginator(this); m_paginator = new FeaturedImagesPaginator(this);
PaginationNotifier *notifier = static_cast<PaginationNotifier*>(m_paginator); PaginationNotifier *notifier = static_cast<PaginationNotifier*>(m_paginator);
@@ -157,6 +158,8 @@ auto FeaturedImagesModel::resetState() -> void
auto FeaturedImagesModel::resetDataModel() -> void auto FeaturedImagesModel::resetDataModel() -> void
{ {
setState(Loading);
//invalidate previous model data //invalidate previous model data
beginRemoveRows(QModelIndex(), 0, m_data.count()); beginRemoveRows(QModelIndex(), 0, m_data.count());
m_data.clear(); m_data.clear();
@@ -172,6 +175,8 @@ auto FeaturedImagesModel::resetDataModel() -> void
} }
endInsertRows(); endInsertRows();
setState(Idle);
} }
auto FeaturedImagesModel::roleNames() const -> QHash<int, QByteArray> auto FeaturedImagesModel::roleNames() const -> QHash<int, QByteArray>
+6
View File
@@ -3,6 +3,8 @@
FeaturedVideosModel::FeaturedVideosModel(QObject *parent) : QAbstractListModel{parent} FeaturedVideosModel::FeaturedVideosModel(QObject *parent) : QAbstractListModel{parent}
{ {
setState(Loading);
m_paginator = new FeaturedVideosPaginator(this); m_paginator = new FeaturedVideosPaginator(this);
PaginationNotifier *notifier = static_cast<PaginationNotifier*>(m_paginator); PaginationNotifier *notifier = static_cast<PaginationNotifier*>(m_paginator);
@@ -145,6 +147,8 @@ auto FeaturedVideosModel::resetState() -> void
auto FeaturedVideosModel::resetDataModel() -> void auto FeaturedVideosModel::resetDataModel() -> void
{ {
setState(Loading);
//invalidate previous model data //invalidate previous model data
beginRemoveRows(QModelIndex(), 0, m_data.count()); beginRemoveRows(QModelIndex(), 0, m_data.count());
m_data.clear(); m_data.clear();
@@ -160,6 +164,8 @@ auto FeaturedVideosModel::resetDataModel() -> void
} }
endInsertRows(); endInsertRows();
setState(Idle);
} }
auto FeaturedVideosModel::roleNames() const -> QHash<int, QByteArray> auto FeaturedVideosModel::roleNames() const -> QHash<int, QByteArray>
+300
View File
@@ -0,0 +1,300 @@
#include "imagesearchmodel.h"
ImageSearchModel::ImageSearchModel(QObject *parent) : QAbstractListModel{parent}
{
setState(Loading);
m_paginator = new ImageSearchPaginator(this);
PaginationNotifier *notifier = static_cast<PaginationNotifier*>(m_paginator);
QObject::connect(
notifier,
&PaginationNotifier::resultsPerPageChanged,
this,
&ImageSearchModel::resultsPerPageChanged
);
QObject::connect(
notifier,
&PaginationNotifier::totalResultsChanged,
this,
&ImageSearchModel::totalResultsChanged
);
QObject::connect(
notifier,
&PaginationNotifier::totalPagesChanged,
this,
&ImageSearchModel::totalPagesChanged
);
QObject::connect(
notifier,
&PaginationNotifier::pageChanged,
this,
&ImageSearchModel::pageChanged
);
QObject::connect(
notifier,
&PaginationNotifier::dataChanged,
this,
&ImageSearchModel::resetDataModel
);
QObject::connect(
m_paginator,
&ImageSearchPaginator::queryChanged,
this,
&ImageSearchModel::queryChanged
);
}
auto ImageSearchModel::rowCount(const QModelIndex &) const -> int
{
if(m_paginator != nullptr)
{
return m_paginator->count();
}
return 0;
}
auto ImageSearchModel::data(const QModelIndex &index, int role) const -> QVariant
{
if(index.row() < 0 || m_paginator == nullptr)
{
return {};
}
ImageCache dataPoint = m_data.at(index.row());
QVariant data;
switch(static_cast<DataRole>(role))
{
case UuidRole:
data = dataPoint.id;
break;
case AuthorRole:
data = dataPoint.photographer;
break;
case AuthorIdRole:
data = dataPoint.photographerId;
break;
case AuthorUrlRole:
data = dataPoint.photographerUrl;
break;
case DescriptionRole:
data = dataPoint.altText;
break;
case ThumbnailRole:
data = dataPoint.sources.value(QString::fromUtf8("tiny"));
break;
case PortraitUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("portrait"));
break;
case LandscapeUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("landscape"));
break;
case SmallUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("small"));
break;
case OriginalUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("original"));
break;
case MediumUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("medium"));
break;
case LargeUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("large"));
break;
case ExtraLargeUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("large2x"));
break;
}
return data;
}
auto ImageSearchModel::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 ImageSearchModel::columnCount(const QModelIndex &) const -> int
{
return 0;
}
auto ImageSearchModel::parent(const QModelIndex &) const -> QModelIndex
{
return {};
}
auto ImageSearchModel::setData(const QModelIndex &, const QVariant &, int) -> bool
{
return false;
}
auto ImageSearchModel::state() const -> ImageSearchModel::State
{
return m_state;
}
auto ImageSearchModel::setState(State state) -> void
{
if (m_state == state)
{
return;
}
m_state = state;
emit stateChanged();
}
auto ImageSearchModel::resetState() -> void
{
setState(Idle);
}
auto ImageSearchModel::resetDataModel() -> void
{
setState(Loading);
//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 ImageCache& data : std::as_const(dataset))
{
m_data.append(data);
}
endInsertRows();
setState(Idle);
}
auto ImageSearchModel::hasNextPage() -> bool
{
return m_paginator->page() < m_paginator->totalPages();
}
auto ImageSearchModel::hasPreviousPage() -> bool
{
return m_paginator->page() > 0;
}
auto ImageSearchModel::roleNames() const -> QHash<int, QByteArray>
{
return m_dataRoles;
}
auto ImageSearchModel::errorString() const -> QString
{
return m_errorString;
}
auto ImageSearchModel::setErrorString(const QString &errorString) -> void
{
if (m_errorString == errorString)
{
return;
}
m_errorString = errorString;
emit errorStringChanged();
}
auto ImageSearchModel::resultsPerPage() const -> qsizetype
{
if(m_paginator != nullptr)
{
return m_paginator->resultsPerPage();
}
return 0;
}
auto ImageSearchModel::setResultsPerPage(qsizetype resultsPerPage) -> void
{
if(m_paginator != nullptr)
{
m_paginator->setResultsPerPage(resultsPerPage);
}
}
auto ImageSearchModel::totalResults() const -> qsizetype
{
if(m_paginator != nullptr)
{
return m_paginator->totalResults();
}
return 0;
}
auto ImageSearchModel::nextPage() const -> void
{
if(m_paginator != nullptr)
{
m_paginator->next();
}
}
auto ImageSearchModel::previousPage() const -> void
{
if(m_paginator != nullptr)
{
m_paginator->previous();
}
}
auto ImageSearchModel::query() const -> QString
{
if(m_paginator != nullptr)
{
return m_paginator->query();
}
return {};
}
auto ImageSearchModel::setQuery(const QString &query) -> void
{
if(m_paginator != nullptr)
{
m_paginator->setQuery(query);
}
}
qsizetype ImageSearchModel::page() const
{
if(m_paginator != nullptr)
{
return m_paginator->page();
}
return 0;
}
auto ImageSearchModel::totalPages() const -> qsizetype
{
if(m_paginator != nullptr)
{
return m_paginator->totalPages();
}
return 0;
}
+335
View File
@@ -0,0 +1,335 @@
/*
* 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 <https://www.gnu.org/licenses/>
*/
#ifndef ImageSearchModel_H
#define ImageSearchModel_H
#include <QObject>
#include <QQmlEngine>
#include <QList>
#include <QStandardPaths>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QJsonParseError>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QAbstractListModel>
#include <QProcess>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QEventLoop>
#include "paginators/imagesearchpaginator.h"
/**
* @brief The ImageSearchModel class
*/
class KOMPLEX_EXPORT ImageSearchModel : 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,
AuthorUrlRole,
DescriptionRole,
ThumbnailRole,
PortraitUrlRole,
LandscapeUrlRole,
SmallUrlRole,
OriginalUrlRole,
MediumUrlRole,
LargeUrlRole,
ExtraLargeUrlRole
};
Q_ENUM(DataRole)
explicit ImageSearchModel(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<int, QByteArray> 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;
/**
* @brief query
* Current search query
* @return
*/
auto query() const -> QString;
/**
* @brief setQuery
* Sets the current
* @param query
*/
auto setQuery(const QString &query) -> 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;
auto hasNextPage() -> bool;
auto hasPreviousPage() -> bool;
signals:
auto stateChanged() -> void;
auto errorStringChanged() -> void;
auto resultsPerPageChanged() -> void;
auto pageChanged() -> void;
auto totalResultsChanged() -> void;
auto totalPagesChanged() -> void;
auto queryChanged() -> void;
private:
static inline const QHash<int, QByteArray> m_dataRoles =
{
{
static_cast<int>(UuidRole),
QByteArray("uuid")
},
{
static_cast<int>(AuthorRole),
QByteArray("author")
},
{
static_cast<int>(AuthorIdRole),
QByteArray("authorId")
},
{
static_cast<int>(AuthorUrlRole),
QByteArray("authorUrl")
},
{
static_cast<int>(DescriptionRole),
QByteArray("description")
},
{
static_cast<int>(ThumbnailRole),
QByteArray("thumbnail")
},
{
static_cast<int>(PortraitUrlRole),
QByteArray("portrait")
},
{
static_cast<int>(LandscapeUrlRole),
QByteArray("landscape")
},
{
static_cast<int>(SmallUrlRole),
QByteArray("small")
},
{
static_cast<int>(OriginalUrlRole),
QByteArray("original")
},
{
static_cast<int>(MediumUrlRole),
QByteArray("medium")
},
{
static_cast<int>(LargeUrlRole),
QByteArray("large")
},
{
static_cast<int>(ExtraLargeUrlRole),
QByteArray("extraLarge")
}
};
State m_state = Idle;
QString m_errorString = QString();
ImageSearchPaginator *m_paginator = nullptr;
mutable QList<ImageCache> 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_PROPERTY(bool hasNextPage READ hasNextPage NOTIFY pageChanged FINAL)
Q_PROPERTY(bool hasPreviousPage READ hasPreviousPage NOTIFY pageChanged FINAL)
Q_PROPERTY(QString query READ query WRITE setQuery NOTIFY queryChanged FINAL)
};
Q_DECLARE_METATYPE(ImageSearchModel)
#endif // ImageSearchModel_H
+4
View File
@@ -3,6 +3,8 @@
NewestPacksModel::NewestPacksModel(QObject *parent) : QAbstractListModel{parent} NewestPacksModel::NewestPacksModel(QObject *parent) : QAbstractListModel{parent}
{ {
setState(Loading);
m_paginator = new NewestPacksPaginator(this); m_paginator = new NewestPacksPaginator(this);
PaginationNotifier *notifier = static_cast<PaginationNotifier*>(m_paginator); PaginationNotifier *notifier = static_cast<PaginationNotifier*>(m_paginator);
@@ -151,6 +153,7 @@ auto NewestPacksModel::resetState() -> void
auto NewestPacksModel::resetDataModel() -> void auto NewestPacksModel::resetDataModel() -> void
{ {
setState(Loading);
//invalidate previous model data //invalidate previous model data
beginRemoveRows(QModelIndex(), 0, m_data.count()); beginRemoveRows(QModelIndex(), 0, m_data.count());
m_data.clear(); m_data.clear();
@@ -166,6 +169,7 @@ auto NewestPacksModel::resetDataModel() -> void
} }
endInsertRows(); endInsertRows();
setState(Idle);
} }
auto NewestPacksModel::roleNames() const -> QHash<int, QByteArray> auto NewestPacksModel::roleNames() const -> QHash<int, QByteArray>
@@ -89,7 +89,6 @@ public:
Q_EMIT queryChanged(); Q_EMIT queryChanged();
reset(); reset();
setOffset(0);
} }
signals: signals:
@@ -16,8 +16,8 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/> * along with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
#ifndef SEARCHIMAGESPAGINATOR_H #ifndef IMAGESEARCHPAGINATOR_H
#define SEARCHIMAGESPAGINATOR_H #define IMAGESEARCHPAGINATOR_H
#include <QObject> #include <QObject>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
@@ -29,14 +29,14 @@
#include <QJsonArray> #include <QJsonArray>
#include <QJsonObject> #include <QJsonObject>
#include "wallpaperpaginator.h" #include "imagepaginator.h"
#include "common/komplex_global.h" #include "common/komplex_global.h"
class KOMPLEX_EXPORT SearchImagesPaginator : public WallpaperPaginator class KOMPLEX_EXPORT ImageSearchPaginator : public ImagePaginator
{ {
public: public:
explicit SearchImagesPaginator(QObject *parent = nullptr) : WallpaperPaginator(parent) explicit ImageSearchPaginator(QObject *parent = nullptr) : ImagePaginator(parent)
{ {
setUri( setUri(
QString::fromUtf8(KOMPLEX_ENDPOINT_IMAGES_SEARCH) QString::fromUtf8(KOMPLEX_ENDPOINT_IMAGES_SEARCH)
@@ -44,4 +44,4 @@ public:
} }
}; };
#endif // SEARCHIMAGESPAGINATOR_H #endif // IMAGESEARCHPAGINATOR_H