Added featured images model, paginator and cache

This commit is contained in:
Digital Artifex
2026-06-17 14:52:15 -04:00
parent ac71b74ed0
commit 8a9417a5e5
9 changed files with 793 additions and 72 deletions
+8 -8
View File
@@ -120,7 +120,6 @@ Item {
delegate: Item { delegate: Item {
width: 256 width: 256
height: 256 height: 256
required property string name
required property string author required property string author
required property string description required property string description
required property string uuid required property string uuid
@@ -130,8 +129,8 @@ Item {
SearchResultItem SearchResultItem
{ {
anchors.fill: parent anchors.fill: parent
title: parent.name title: parent.author
author: parent.author author: qsTr("Pexels Images")
description: parent.description description: parent.description
thumbnail: parent.thumbnail thumbnail: parent.thumbnail
uuid: parent.uuid uuid: parent.uuid
@@ -176,17 +175,18 @@ Item {
delegate: Item { delegate: Item {
width: 256 width: 256
height: 256 height: 256
required property string name
required property string author
required property string description
required property string uuid required property string uuid
required property string thumbnail required property string author
required property string authorId required property string authorId
required property string authorUrl
required property string description
required property string thumbnail
required property string original
SearchResultItem SearchResultItem
{ {
anchors.fill: parent anchors.fill: parent
title: parent.name title: "Pexels Photo"
author: parent.author author: parent.author
description: parent.description description: parent.description
thumbnail: parent.thumbnail thumbnail: parent.thumbnail
+2
View File
@@ -46,6 +46,8 @@ qt_add_qml_module(
SOURCES featuredvideosmodel.h SOURCES featuredvideosmodel.h
SOURCES featuredvideosmodel.cpp SOURCES featuredvideosmodel.cpp
SOURCES paginators/featuredvideospaginator.h SOURCES paginators/featuredvideospaginator.h
SOURCES common/imagecache.h
SOURCES paginators/imagepaginator.h
) )
+337
View File
@@ -0,0 +1,337 @@
{
"page": 1,
"per_page": 15,
"photos": [
{
"alt": "A solitary church atop a hill in Iceland set against a dramatic cloudy sky in black and white.",
"avg_color": "#777777",
"height": 4000,
"id": 37953511,
"liked": false,
"photographer": "Alexis B",
"photographer_id": 1699196,
"photographer_url": "https://www.pexels.com/@alexis-b-1699196",
"src": {
"landscape": "https://images.pexels.com/photos/37953511/pexels-photo-37953511.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=627&w=1200",
"large": "https://images.pexels.com/photos/37953511/pexels-photo-37953511.jpeg?auto=compress&cs=tinysrgb&h=650&w=940",
"large2x": "https://images.pexels.com/photos/37953511/pexels-photo-37953511.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
"medium": "https://images.pexels.com/photos/37953511/pexels-photo-37953511.jpeg?auto=compress&cs=tinysrgb&h=350",
"original": "https://images.pexels.com/photos/37953511/pexels-photo-37953511.jpeg",
"portrait": "https://images.pexels.com/photos/37953511/pexels-photo-37953511.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=1200&w=800",
"small": "https://images.pexels.com/photos/37953511/pexels-photo-37953511.jpeg?auto=compress&cs=tinysrgb&h=130",
"tiny": "https://images.pexels.com/photos/37953511/pexels-photo-37953511.jpeg?auto=compress&cs=tinysrgb&dpr=1&fit=crop&h=200&w=280"
},
"url": "https://www.pexels.com/photo/isolated-church-on-icelandic-hill-in-black-and-white-37953511/",
"width": 6000
},
{
"alt": "A stylish wedding couple poses in a historic Istanbul location, embodying romance and elegance.",
"avg_color": "#242424",
"height": 6304,
"id": 38109250,
"liked": false,
"photographer": "Mina Fotoğraf",
"photographer_id": 2162259056,
"photographer_url": "https://www.pexels.com/@mina-fotograf-2162259056",
"src": {
"landscape": "https://images.pexels.com/photos/38109250/pexels-photo-38109250.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=627&w=1200",
"large": "https://images.pexels.com/photos/38109250/pexels-photo-38109250.jpeg?auto=compress&cs=tinysrgb&h=650&w=940",
"large2x": "https://images.pexels.com/photos/38109250/pexels-photo-38109250.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
"medium": "https://images.pexels.com/photos/38109250/pexels-photo-38109250.jpeg?auto=compress&cs=tinysrgb&h=350",
"original": "https://images.pexels.com/photos/38109250/pexels-photo-38109250.jpeg",
"portrait": "https://images.pexels.com/photos/38109250/pexels-photo-38109250.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=1200&w=800",
"small": "https://images.pexels.com/photos/38109250/pexels-photo-38109250.jpeg?auto=compress&cs=tinysrgb&h=130",
"tiny": "https://images.pexels.com/photos/38109250/pexels-photo-38109250.jpeg?auto=compress&cs=tinysrgb&dpr=1&fit=crop&h=200&w=280"
},
"url": "https://www.pexels.com/photo/elegant-wedding-couple-in-historic-istanbul-venue-38109250/",
"width": 4203
},
{
"alt": "Black and white cityscape capturing two people walking along a quaint European street at twilight.",
"avg_color": "#434343",
"height": 7728,
"id": 37644555,
"liked": false,
"photographer": "Alec Doualetas",
"photographer_id": 61739,
"photographer_url": "https://www.pexels.com/@alecdoua",
"src": {
"landscape": "https://images.pexels.com/photos/37644555/pexels-photo-37644555.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=627&w=1200",
"large": "https://images.pexels.com/photos/37644555/pexels-photo-37644555.jpeg?auto=compress&cs=tinysrgb&h=650&w=940",
"large2x": "https://images.pexels.com/photos/37644555/pexels-photo-37644555.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
"medium": "https://images.pexels.com/photos/37644555/pexels-photo-37644555.jpeg?auto=compress&cs=tinysrgb&h=350",
"original": "https://images.pexels.com/photos/37644555/pexels-photo-37644555.jpeg",
"portrait": "https://images.pexels.com/photos/37644555/pexels-photo-37644555.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=1200&w=800",
"small": "https://images.pexels.com/photos/37644555/pexels-photo-37644555.jpeg?auto=compress&cs=tinysrgb&h=130",
"tiny": "https://images.pexels.com/photos/37644555/pexels-photo-37644555.jpeg?auto=compress&cs=tinysrgb&dpr=1&fit=crop&h=200&w=280"
},
"url": "https://www.pexels.com/photo/charming-urban-street-view-with-evening-stroll-37644555/",
"width": 5152
},
{
"alt": "Monochrome portrait of a young man in a denim jacket seated indoors.",
"avg_color": "#626262",
"height": 6000,
"id": 38110448,
"liked": false,
"photographer": "Portraits By PB",
"photographer_id": 2162091442,
"photographer_url": "https://www.pexels.com/@pbportraits",
"src": {
"landscape": "https://images.pexels.com/photos/38110448/pexels-photo-38110448.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=627&w=1200",
"large": "https://images.pexels.com/photos/38110448/pexels-photo-38110448.jpeg?auto=compress&cs=tinysrgb&h=650&w=940",
"large2x": "https://images.pexels.com/photos/38110448/pexels-photo-38110448.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
"medium": "https://images.pexels.com/photos/38110448/pexels-photo-38110448.jpeg?auto=compress&cs=tinysrgb&h=350",
"original": "https://images.pexels.com/photos/38110448/pexels-photo-38110448.jpeg",
"portrait": "https://images.pexels.com/photos/38110448/pexels-photo-38110448.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=1200&w=800",
"small": "https://images.pexels.com/photos/38110448/pexels-photo-38110448.jpeg?auto=compress&cs=tinysrgb&h=130",
"tiny": "https://images.pexels.com/photos/38110448/pexels-photo-38110448.jpeg?auto=compress&cs=tinysrgb&dpr=1&fit=crop&h=200&w=280"
},
"url": "https://www.pexels.com/photo/black-and-white-portrait-of-young-man-in-denim-38110448/",
"width": 4000
},
{
"alt": "A dynamic black and white photo capturing reflections on modern building windows.",
"avg_color": "#6B6B6B",
"height": 3840,
"id": 37488903,
"liked": false,
"photographer": "wal_ 172619",
"photographer_id": 2156618639,
"photographer_url": "https://www.pexels.com/@wal_-172619-2156618639",
"src": {
"landscape": "https://images.pexels.com/photos/37488903/pexels-photo-37488903.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=627&w=1200",
"large": "https://images.pexels.com/photos/37488903/pexels-photo-37488903.jpeg?auto=compress&cs=tinysrgb&h=650&w=940",
"large2x": "https://images.pexels.com/photos/37488903/pexels-photo-37488903.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
"medium": "https://images.pexels.com/photos/37488903/pexels-photo-37488903.jpeg?auto=compress&cs=tinysrgb&h=350",
"original": "https://images.pexels.com/photos/37488903/pexels-photo-37488903.jpeg",
"portrait": "https://images.pexels.com/photos/37488903/pexels-photo-37488903.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=1200&w=800",
"small": "https://images.pexels.com/photos/37488903/pexels-photo-37488903.jpeg?auto=compress&cs=tinysrgb&h=130",
"tiny": "https://images.pexels.com/photos/37488903/pexels-photo-37488903.jpeg?auto=compress&cs=tinysrgb&dpr=1&fit=crop&h=200&w=280"
},
"url": "https://www.pexels.com/photo/abstract-black-and-white-modern-building-reflection-37488903/",
"width": 5760
},
{
"alt": "Black and white photo of cyclists racing under a bridge, casting shadows on a concrete wall.",
"avg_color": "#666666",
"height": 3648,
"id": 37730682,
"liked": false,
"photographer": "Emeric Laperriere",
"photographer_id": 3009913,
"photographer_url": "https://www.pexels.com/@emeric07",
"src": {
"landscape": "https://images.pexels.com/photos/37730682/pexels-photo-37730682.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=627&w=1200",
"large": "https://images.pexels.com/photos/37730682/pexels-photo-37730682.jpeg?auto=compress&cs=tinysrgb&h=650&w=940",
"large2x": "https://images.pexels.com/photos/37730682/pexels-photo-37730682.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
"medium": "https://images.pexels.com/photos/37730682/pexels-photo-37730682.jpeg?auto=compress&cs=tinysrgb&h=350",
"original": "https://images.pexels.com/photos/37730682/pexels-photo-37730682.jpeg",
"portrait": "https://images.pexels.com/photos/37730682/pexels-photo-37730682.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=1200&w=800",
"small": "https://images.pexels.com/photos/37730682/pexels-photo-37730682.jpeg?auto=compress&cs=tinysrgb&h=130",
"tiny": "https://images.pexels.com/photos/37730682/pexels-photo-37730682.jpeg?auto=compress&cs=tinysrgb&dpr=1&fit=crop&h=200&w=280"
},
"url": "https://www.pexels.com/photo/cyclists-racing-under-urban-bridge-in-shadows-37730682/",
"width": 5472
},
{
"alt": "Two hikers standing atop a grassy hill in Scotland, captured in striking black and white.",
"avg_color": "#9C9C9C",
"height": 4530,
"id": 33777882,
"liked": false,
"photographer": "Phil Desforges",
"photographer_id": 1226422,
"photographer_url": "https://www.pexels.com/@storybyphil",
"src": {
"landscape": "https://images.pexels.com/photos/33777882/pexels-photo-33777882.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=627&w=1200",
"large": "https://images.pexels.com/photos/33777882/pexels-photo-33777882.jpeg?auto=compress&cs=tinysrgb&h=650&w=940",
"large2x": "https://images.pexels.com/photos/33777882/pexels-photo-33777882.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
"medium": "https://images.pexels.com/photos/33777882/pexels-photo-33777882.jpeg?auto=compress&cs=tinysrgb&h=350",
"original": "https://images.pexels.com/photos/33777882/pexels-photo-33777882.jpeg",
"portrait": "https://images.pexels.com/photos/33777882/pexels-photo-33777882.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=1200&w=800",
"small": "https://images.pexels.com/photos/33777882/pexels-photo-33777882.jpeg?auto=compress&cs=tinysrgb&h=130",
"tiny": "https://images.pexels.com/photos/33777882/pexels-photo-33777882.jpeg?auto=compress&cs=tinysrgb&dpr=1&fit=crop&h=200&w=280"
},
"url": "https://www.pexels.com/photo/stunning-black-and-white-hiking-scene-in-scotland-33777882/",
"width": 3624
},
{
"alt": "Silhouettes of two children playing indoors by a large window with a view of a ship in İstanbul.",
"avg_color": "#A4A4A4",
"height": 4209,
"id": 28319243,
"liked": false,
"photographer": "İrem Dur",
"photographer_id": 1175044181,
"photographer_url": "https://www.pexels.com/@i-rem-dur-1175044181",
"src": {
"landscape": "https://images.pexels.com/photos/28319243/pexels-photo-28319243.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=627&w=1200",
"large": "https://images.pexels.com/photos/28319243/pexels-photo-28319243.jpeg?auto=compress&cs=tinysrgb&h=650&w=940",
"large2x": "https://images.pexels.com/photos/28319243/pexels-photo-28319243.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
"medium": "https://images.pexels.com/photos/28319243/pexels-photo-28319243.jpeg?auto=compress&cs=tinysrgb&h=350",
"original": "https://images.pexels.com/photos/28319243/pexels-photo-28319243.jpeg",
"portrait": "https://images.pexels.com/photos/28319243/pexels-photo-28319243.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=1200&w=800",
"small": "https://images.pexels.com/photos/28319243/pexels-photo-28319243.jpeg?auto=compress&cs=tinysrgb&h=130",
"tiny": "https://images.pexels.com/photos/28319243/pexels-photo-28319243.jpeg?auto=compress&cs=tinysrgb&dpr=1&fit=crop&h=200&w=280"
},
"url": "https://www.pexels.com/photo/two-people-are-walking-through-a-glass-door-28319243/",
"width": 6313
},
{
"alt": "Delicate cherry blossoms captured in black and white, showcasing their elegant beauty in springtime.",
"avg_color": "#797979",
"height": 3089,
"id": 27661694,
"liked": false,
"photographer": "Anna Holodna",
"photographer_id": 237271098,
"photographer_url": "https://www.pexels.com/@holodna",
"src": {
"landscape": "https://images.pexels.com/photos/27661694/pexels-photo-27661694.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=627&w=1200",
"large": "https://images.pexels.com/photos/27661694/pexels-photo-27661694.jpeg?auto=compress&cs=tinysrgb&h=650&w=940",
"large2x": "https://images.pexels.com/photos/27661694/pexels-photo-27661694.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
"medium": "https://images.pexels.com/photos/27661694/pexels-photo-27661694.jpeg?auto=compress&cs=tinysrgb&h=350",
"original": "https://images.pexels.com/photos/27661694/pexels-photo-27661694.jpeg",
"portrait": "https://images.pexels.com/photos/27661694/pexels-photo-27661694.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=1200&w=800",
"small": "https://images.pexels.com/photos/27661694/pexels-photo-27661694.jpeg?auto=compress&cs=tinysrgb&h=130",
"tiny": "https://images.pexels.com/photos/27661694/pexels-photo-27661694.jpeg?auto=compress&cs=tinysrgb&dpr=1&fit=crop&h=200&w=280"
},
"url": "https://www.pexels.com/photo/black-and-white-photograph-of-flowers-in-bloom-27661694/",
"width": 2048
},
{
"alt": "Textured sand dunes in Monahans, Texas captured in striking black and white.",
"avg_color": "#8E8E8E",
"height": 6336,
"id": 37152401,
"liked": false,
"photographer": "Phil Evenden",
"photographer_id": 69038984,
"photographer_url": "https://www.pexels.com/@philevenphotos",
"src": {
"landscape": "https://images.pexels.com/photos/37152401/pexels-photo-37152401.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=627&w=1200",
"large": "https://images.pexels.com/photos/37152401/pexels-photo-37152401.jpeg?auto=compress&cs=tinysrgb&h=650&w=940",
"large2x": "https://images.pexels.com/photos/37152401/pexels-photo-37152401.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
"medium": "https://images.pexels.com/photos/37152401/pexels-photo-37152401.jpeg?auto=compress&cs=tinysrgb&h=350",
"original": "https://images.pexels.com/photos/37152401/pexels-photo-37152401.jpeg",
"portrait": "https://images.pexels.com/photos/37152401/pexels-photo-37152401.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=1200&w=800",
"small": "https://images.pexels.com/photos/37152401/pexels-photo-37152401.jpeg?auto=compress&cs=tinysrgb&h=130",
"tiny": "https://images.pexels.com/photos/37152401/pexels-photo-37152401.jpeg?auto=compress&cs=tinysrgb&dpr=1&fit=crop&h=200&w=280"
},
"url": "https://www.pexels.com/photo/monahans-sandhills-ripples-in-black-and-white-37152401/",
"width": 9504
},
{
"alt": "Black and white photo of a bicycle leaning against a brick building in Copenhagen.",
"avg_color": "#707070",
"height": 3918,
"id": 20643887,
"liked": false,
"photographer": "Mihai Vlasceanu",
"photographer_id": 602123,
"photographer_url": "https://www.pexels.com/@vlasceanu",
"src": {
"landscape": "https://images.pexels.com/photos/20643887/pexels-photo-20643887.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=627&w=1200",
"large": "https://images.pexels.com/photos/20643887/pexels-photo-20643887.jpeg?auto=compress&cs=tinysrgb&h=650&w=940",
"large2x": "https://images.pexels.com/photos/20643887/pexels-photo-20643887.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
"medium": "https://images.pexels.com/photos/20643887/pexels-photo-20643887.jpeg?auto=compress&cs=tinysrgb&h=350",
"original": "https://images.pexels.com/photos/20643887/pexels-photo-20643887.jpeg",
"portrait": "https://images.pexels.com/photos/20643887/pexels-photo-20643887.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=1200&w=800",
"small": "https://images.pexels.com/photos/20643887/pexels-photo-20643887.jpeg?auto=compress&cs=tinysrgb&h=130",
"tiny": "https://images.pexels.com/photos/20643887/pexels-photo-20643887.jpeg?auto=compress&cs=tinysrgb&dpr=1&fit=crop&h=200&w=280"
},
"url": "https://www.pexels.com/photo/bicycle-near-building-wall-in-black-and-white-20643887/",
"width": 5602
},
{
"alt": "A contemplative moment captured at a park in Honolulu with silhouetted trees and a bench.",
"avg_color": "#36332E",
"height": 5762,
"id": 37134995,
"liked": false,
"photographer": "Roy Serafin",
"photographer_id": 2157973447,
"photographer_url": "https://www.pexels.com/@roythephotographer",
"src": {
"landscape": "https://images.pexels.com/photos/37134995/pexels-photo-37134995.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=627&w=1200",
"large": "https://images.pexels.com/photos/37134995/pexels-photo-37134995.jpeg?auto=compress&cs=tinysrgb&h=650&w=940",
"large2x": "https://images.pexels.com/photos/37134995/pexels-photo-37134995.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
"medium": "https://images.pexels.com/photos/37134995/pexels-photo-37134995.jpeg?auto=compress&cs=tinysrgb&h=350",
"original": "https://images.pexels.com/photos/37134995/pexels-photo-37134995.jpeg",
"portrait": "https://images.pexels.com/photos/37134995/pexels-photo-37134995.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=1200&w=800",
"small": "https://images.pexels.com/photos/37134995/pexels-photo-37134995.jpeg?auto=compress&cs=tinysrgb&h=130",
"tiny": "https://images.pexels.com/photos/37134995/pexels-photo-37134995.jpeg?auto=compress&cs=tinysrgb&dpr=1&fit=crop&h=200&w=280"
},
"url": "https://www.pexels.com/photo/serene-silhouette-in-honolulu-park-37134995/",
"width": 3839
},
{
"alt": "Panoramic view of rugged cliffs and ocean in La Gomera, showcasing dramatic landscapes.",
"avg_color": "#6E7C7F",
"height": 6000,
"id": 34145802,
"liked": false,
"photographer": "Liisbet Luup",
"photographer_id": 121486327,
"photographer_url": "https://www.pexels.com/@liisbet-luup-121486327",
"src": {
"landscape": "https://images.pexels.com/photos/34145802/pexels-photo-34145802.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=627&w=1200",
"large": "https://images.pexels.com/photos/34145802/pexels-photo-34145802.jpeg?auto=compress&cs=tinysrgb&h=650&w=940",
"large2x": "https://images.pexels.com/photos/34145802/pexels-photo-34145802.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
"medium": "https://images.pexels.com/photos/34145802/pexels-photo-34145802.jpeg?auto=compress&cs=tinysrgb&h=350",
"original": "https://images.pexels.com/photos/34145802/pexels-photo-34145802.jpeg",
"portrait": "https://images.pexels.com/photos/34145802/pexels-photo-34145802.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=1200&w=800",
"small": "https://images.pexels.com/photos/34145802/pexels-photo-34145802.jpeg?auto=compress&cs=tinysrgb&h=130",
"tiny": "https://images.pexels.com/photos/34145802/pexels-photo-34145802.jpeg?auto=compress&cs=tinysrgb&dpr=1&fit=crop&h=200&w=280"
},
"url": "https://www.pexels.com/photo/dramatic-cliffs-and-ocean-view-canary-islands-34145802/",
"width": 4000
},
{
"alt": "Lush pine trees in a tranquil forest setting, capturing the beauty of Finnish nature.",
"avg_color": "#6B645A",
"height": 3637,
"id": 38101841,
"liked": false,
"photographer": "Alice Tran",
"photographer_id": 2162267306,
"photographer_url": "https://www.pexels.com/@alice-tran-2162267306",
"src": {
"landscape": "https://images.pexels.com/photos/38101841/pexels-photo-38101841.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=627&w=1200",
"large": "https://images.pexels.com/photos/38101841/pexels-photo-38101841.jpeg?auto=compress&cs=tinysrgb&h=650&w=940",
"large2x": "https://images.pexels.com/photos/38101841/pexels-photo-38101841.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
"medium": "https://images.pexels.com/photos/38101841/pexels-photo-38101841.jpeg?auto=compress&cs=tinysrgb&h=350",
"original": "https://images.pexels.com/photos/38101841/pexels-photo-38101841.jpeg",
"portrait": "https://images.pexels.com/photos/38101841/pexels-photo-38101841.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=1200&w=800",
"small": "https://images.pexels.com/photos/38101841/pexels-photo-38101841.jpeg?auto=compress&cs=tinysrgb&h=130",
"tiny": "https://images.pexels.com/photos/38101841/pexels-photo-38101841.jpeg?auto=compress&cs=tinysrgb&dpr=1&fit=crop&h=200&w=280"
},
"url": "https://www.pexels.com/photo/serene-pine-forest-in-finnish-wilderness-38101841/",
"width": 2433
},
{
"alt": "A scenic aerial view of boats anchored in a tranquil blue bay surrounded by lush hills.",
"avg_color": "#3B6571",
"height": 7008,
"id": 33802711,
"liked": false,
"photographer": "Sueda Dilli",
"photographer_id": 143459271,
"photographer_url": "https://www.pexels.com/@suedadilli",
"src": {
"landscape": "https://images.pexels.com/photos/33802711/pexels-photo-33802711.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=627&w=1200",
"large": "https://images.pexels.com/photos/33802711/pexels-photo-33802711.jpeg?auto=compress&cs=tinysrgb&h=650&w=940",
"large2x": "https://images.pexels.com/photos/33802711/pexels-photo-33802711.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
"medium": "https://images.pexels.com/photos/33802711/pexels-photo-33802711.jpeg?auto=compress&cs=tinysrgb&h=350",
"original": "https://images.pexels.com/photos/33802711/pexels-photo-33802711.jpeg",
"portrait": "https://images.pexels.com/photos/33802711/pexels-photo-33802711.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=1200&w=800",
"small": "https://images.pexels.com/photos/33802711/pexels-photo-33802711.jpeg?auto=compress&cs=tinysrgb&h=130",
"tiny": "https://images.pexels.com/photos/33802711/pexels-photo-33802711.jpeg?auto=compress&cs=tinysrgb&dpr=1&fit=crop&h=200&w=280"
},
"url": "https://www.pexels.com/photo/serene-aerial-view-of-boats-in-a-blue-bay-33802711/",
"width": 4672
}
],
"total_results": 64520
}
+48
View File
@@ -0,0 +1,48 @@
#ifndef IMAGECACHE_H
#define IMAGECACHE_H
#include "komplex_global.h"
#include <QObject>
#include <QMap>
#include <QString>
struct KOMPLEX_EXPORT ImageCache
{
QString altText;
QByteArray averageColor;
qsizetype height;
qsizetype width;
qint64 id;
bool liked; //probably not needed
QString photographer;
QString photographerUrl;
qint64 photographerId;
QMap<QString, QString> sources;
QString url;
auto operator == (const ImageCache &other) const -> bool
{
return (
other.altText == altText &&
other.averageColor == averageColor &&
other.height == height &&
other.width == width &&
other.id == id &&
other.liked == liked &&
other.photographer == photographer &&
other.photographerUrl == photographerUrl &&
other.photographerId == photographerId &&
other.sources == sources &&
other.url == url
);
}
auto operator != (const ImageCache &other) const -> bool
{
return !(other == *this);
}
};
Q_DECLARE_METATYPE(ImageCache)
#endif // IMAGECACHE_H
+31 -25
View File
@@ -59,44 +59,50 @@ auto FeaturedImagesModel::data(const QModelIndex &index, int role) const -> QVar
return {}; return {};
} }
WallpaperCache dataPoint = m_data.at(index.row()); ImageCache dataPoint = m_data.at(index.row());
QVariant data; QVariant data;
switch(static_cast<DataRole>(role)) switch(static_cast<DataRole>(role))
{ {
case UuidRole: case UuidRole:
data = dataPoint.uuid; data = dataPoint.id;
break; break;
case AuthorRole: case AuthorRole:
data = dataPoint.author; data = dataPoint.photographer;
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; break;
case AuthorIdRole: case AuthorIdRole:
data = dataPoint.authorId; data = dataPoint.photographerId;
break; break;
case PriceRole: case AuthorUrlRole:
data = dataPoint.price; data = dataPoint.photographerUrl;
break; break;
case CurrencyRole: case DescriptionRole:
data = dataPoint.currency; data = dataPoint.altText;
break; break;
case DownloadCountRole: case ThumbnailRole:
data = dataPoint.downloadCount; data = dataPoint.sources.value(QString::fromUtf8("tiny"));
break; break;
case TypeRole: case PortraitUrlRole:
data = dataPoint.type; 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; break;
} }
@@ -160,7 +166,7 @@ auto FeaturedImagesModel::resetDataModel() -> void
beginInsertRows(QModelIndex(), 0, m_paginator->count() - 1); beginInsertRows(QModelIndex(), 0, m_paginator->count() - 1);
auto dataset = m_paginator->data(); auto dataset = m_paginator->data();
for(const WallpaperCache& data : std::as_const(dataset)) for(const ImageCache& data : std::as_const(dataset))
{ {
m_data.append(data); m_data.append(data);
} }
+31 -21
View File
@@ -65,14 +65,16 @@ public:
UuidRole = Qt::UserRole + 1, UuidRole = Qt::UserRole + 1,
AuthorRole, AuthorRole,
AuthorIdRole, AuthorIdRole,
AuthorUrlRole,
DescriptionRole, DescriptionRole,
NameRole,
ThumbnailRole, ThumbnailRole,
CreatedDateRole, PortraitUrlRole,
PriceRole, LandscapeUrlRole,
CurrencyRole, SmallUrlRole,
DownloadCountRole, OriginalUrlRole,
TypeRole MediumUrlRole,
LargeUrlRole,
ExtraLargeUrlRole
}; };
Q_ENUM(DataRole) Q_ENUM(DataRole)
@@ -251,36 +253,44 @@ private:
QByteArray("authorId") QByteArray("authorId")
}, },
{ {
static_cast<int>(DescriptionRole), static_cast<int>(AuthorUrlRole),
QByteArray("description") QByteArray("authorUrl")
}, },
{ {
static_cast<int>(NameRole), static_cast<int>(DescriptionRole),
QByteArray("name") QByteArray("description")
}, },
{ {
static_cast<int>(ThumbnailRole), static_cast<int>(ThumbnailRole),
QByteArray("thumbnail") QByteArray("thumbnail")
}, },
{ {
static_cast<int>(CreatedDateRole), static_cast<int>(PortraitUrlRole),
QByteArray("createdDate") QByteArray("portrait")
}, },
{ {
static_cast<int>(PriceRole), static_cast<int>(LandscapeUrlRole),
QByteArray("price") QByteArray("landscape")
}, },
{ {
static_cast<int>(CurrencyRole), static_cast<int>(SmallUrlRole),
QByteArray("currency") QByteArray("small")
}, },
{ {
static_cast<int>(DownloadCountRole), static_cast<int>(OriginalUrlRole),
QByteArray("downloadCount") QByteArray("original")
}, },
{ {
static_cast<int>(TypeRole), static_cast<int>(MediumUrlRole),
QByteArray("type") QByteArray("medium")
},
{
static_cast<int>(LargeUrlRole),
QByteArray("large")
},
{
static_cast<int>(ExtraLargeUrlRole),
QByteArray("extraLarge")
} }
}; };
State m_state = Idle; State m_state = Idle;
@@ -289,7 +299,7 @@ private:
FeaturedImagesPaginator *m_paginator = nullptr; FeaturedImagesPaginator *m_paginator = nullptr;
mutable QList<WallpaperCache> m_data; mutable QList<ImageCache> m_data;
Q_PROPERTY(State state READ state WRITE setState RESET resetState NOTIFY stateChanged FINAL) 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(QString errorString READ errorString WRITE setErrorString NOTIFY errorStringChanged FINAL)
@@ -29,12 +29,12 @@
#include <QJsonArray> #include <QJsonArray>
#include <QJsonObject> #include <QJsonObject>
#include "wallpaperpaginator.h" #include "imagepaginator.h"
class KOMPLEX_EXPORT FeaturedImagesPaginator : public WallpaperPaginator class KOMPLEX_EXPORT FeaturedImagesPaginator : public ImagePaginator
{ {
public: public:
explicit FeaturedImagesPaginator(QObject *parent = nullptr) : WallpaperPaginator(parent) explicit FeaturedImagesPaginator(QObject *parent = nullptr) : ImagePaginator(parent)
{ {
setUri( setUri(
QString::fromUtf8(KOMPLEX_ENDPOINT_IMAGES_FEATURED) QString::fromUtf8(KOMPLEX_ENDPOINT_IMAGES_FEATURED)
@@ -0,0 +1,317 @@
/*
* 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 IMAGEPAGINATOR_H
#define IMAGEPAGINATOR_H
#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QEventLoop>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QJsonArray>
#include <QJsonObject>
#include "common/logging.h"
#include "common/paginator.h"
#include "common/imagecache.h"
#include "common/coreservices.h"
/**
* @brief The ImagePaginator class
* Most of the media endpoints for the Komplex API have the same data structure so
* child classes can make use of this generic controller where possible
*/
class KOMPLEX_EXPORT ImagePaginator : public Paginator<ImageCache>
{
Q_OBJECT
public:
explicit ImagePaginator(QObject *parent = nullptr) : Paginator<ImageCache>(parent)
{
SlidingCacheController<ImageCache> *controller = this->controller();
controller->setFetch(
std::bind(&ImagePaginator::fetch, this, std::placeholders::_1, std::placeholders::_2)
);
}
/**
* @brief uri
* URI of the API Endpoint. This is set by the child class
* @return
*/
[[nodiscard]]
auto uri() const -> QString
{
return m_uri;
}
/**
* @brief query
* Current query string, if the API Endpoint supports queries
* @return
*/
[[nodiscard]]
auto query() const -> QString
{
return m_query;
}
/**
* @brief setQuery
* Sets the new query string and updates the controller
* @param query
*/
auto setQuery(const QString &query) -> void
{
if(query == m_query)
{
return;
}
m_query = query;
Q_EMIT queryChanged();
reset();
setOffset(0);
}
signals:
/**
* @brief uriChanged
*/
auto uriChanged() -> void;
/**
* @brief queryChanged
*/
auto queryChanged() -> void;
protected:
/**
* @brief fetch
* Used to fetch results from the API endpoint
* for the CacheController. It is intended to be passed
* to the Cache Controller not meant to be called directly.
* @param offset Offset index to start the results from
* @param limit Number of results to attempt to fetch
* @return FetchResult<ImageCache>
*/
[[nodiscard]]
auto fetch(
qsizetype offset,
qsizetype limit
) const -> FetchResult<ImageCache>
{
LOG_ERROR_X(m_uri.isEmpty(),
"ImagePaginator::fetch",
"Uri not set",
{}
);
QUrl url(
QString::fromUtf8("%1/%2/%3").arg(
KOMPLEX_API_HOST,
KOMPLEX_API_VERSION,
m_uri
)
);
QNetworkRequest request(url);
request.setHeaders(
QHttpHeaders::fromMultiMap(
{
{
QByteArray("query"),
m_query.toUtf8()
},
{
QByteArray("offset"),
QString::number(offset).toUtf8()
},
{
QByteArray("limit"),
QString::number(limit).toUtf8()
},
{
QByteArray("session_token"),
CoreServices::sessionToken()
}
}
)
);
QNetworkReply *reply = getNetworkReply(request);
if(reply == nullptr)
{
return {};
}
QJsonDocument document = getDocument(reply);
QJsonObject rootObject = document.object();
const QJsonArray resultsArray = rootObject.value(QString::fromUtf8("photos")).toArray();
QList<ImageCache> fetchedResults;
for(const auto result : resultsArray)
{
if(!result.isObject())
{
continue;
}
QJsonObject resultObject = result.toObject();
const QVariantMap sources = resultObject.value(QString::fromUtf8("src")).toObject().toVariantMap();
QMap<QString, QString> sourceMap;
QMapIterator<QString,QVariant> sourceIterator(sources);
while(sourceIterator.hasNext())
{
sourceIterator.next();
sourceMap.insert(sourceIterator.key(), sourceIterator.value().toString());
}
fetchedResults.append(
{
resultObject.value(QString::fromUtf8("alt")).toString(),
resultObject.value(QString::fromUtf8("avg_color")).toString().toUtf8(),
resultObject.value(QString::fromUtf8("height")).toInteger(),
resultObject.value(QString::fromUtf8("width")).toInteger(),
resultObject.value(QString::fromUtf8("id")).toInteger(),
resultObject.value(QString::fromUtf8("liked")).toBool(),
resultObject.value(QString::fromUtf8("photographer")).toString(),
resultObject.value(QString::fromUtf8("photographer_url")).toString(),
resultObject.value(QString::fromUtf8("photographer_id")).toInteger(),
sourceMap,
resultObject.value(QString::fromUtf8("url")).toString()
}
);
}
FetchResult<ImageCache> result
{
static_cast<qsizetype>(rootObject.value(QString::fromUtf8("total_results")).toInteger()),
fetchedResults.count(),
std::move(fetchedResults)
};
return std::move(result);
}
/**
* @brief getNetworkReply
* Processes the request and blocks until completion.
* @param request
* @return
*/
[[nodiscard]]
auto getNetworkReply(const QNetworkRequest &request) const -> QNetworkReply*
{
QEventLoop loop;
QWeakPointer<QNetworkAccessManager> managerReference = CoreServices::networkAccessManager();
QSharedPointer<QNetworkAccessManager> manager = managerReference.toStrongRef();
LOG_ERROR_X(!manager,
"NewestPacksPaginator::getNetworkReply",
"Network manager reference has already been deleted",
nullptr
);
QNetworkReply *reply = manager->post(request, nullptr);
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
QObject::connect(reply, &QNetworkReply::errorOccurred, &loop,
[&loop](QNetworkReply::NetworkError error) -> void
{
loop.quit();
}
);
if(!reply->isFinished())
{
loop.exec();
}
LOG_ERROR_X(reply->error() != QNetworkReply::NoError,
"NewestPacksPaginator::getNetworkReply",
reply->errorString().toStdString().c_str(),
nullptr
);
return reply;
}
/**
* @brief getDocument
* Extracts the JSON document from the server reply
* @param reply
* @return
*/
[[nodiscard]]
auto getDocument(QNetworkReply *reply) const -> QJsonDocument
{
QByteArray data = reply->readAll();
QJsonParseError documentError;
QJsonDocument document = QJsonDocument::fromJson(data, &documentError);
LOG_ERROR_X(documentError.error != QJsonParseError::NoError,
"NewestPacksPaginator::getDocument",
documentError.errorString().toStdString().c_str(),
{}
);
return document;
}
/**
* @brief setUri
* Sets the API Endpoint URI to fetch from
* @param uri
*/
auto setUri(const QString &uri) -> void
{
if(uri == m_uri)
{
return;
}
m_uri = uri;
Q_EMIT uriChanged();
}
private:
/**
* @brief m_uri
* API Endpoint URI
*/
QString m_uri;
/**
* @brief m_query
* The query string used in fetch operations
*/
QString m_query;
};
#endif // IMAGEPAGINATOR_H
@@ -138,19 +138,19 @@ protected:
QHttpHeaders::fromMultiMap( QHttpHeaders::fromMultiMap(
{ {
{ {
QByteArray("Query"), QByteArray("query"),
m_query.toUtf8() m_query.toUtf8()
}, },
{ {
QByteArray("Offset"), QByteArray("offset"),
QString::number(offset).toUtf8() QString::number(offset).toUtf8()
}, },
{ {
QByteArray("Limit"), QByteArray("limit"),
QString::number(limit).toUtf8() QString::number(limit).toUtf8()
}, },
{ {
QByteArray("SessionToken"), QByteArray("session_token"),
CoreServices::sessionToken() CoreServices::sessionToken()
} }
} }
@@ -266,8 +266,9 @@ protected:
[[nodiscard]] [[nodiscard]]
auto getDocument(QNetworkReply *reply) const -> QJsonDocument auto getDocument(QNetworkReply *reply) const -> QJsonDocument
{ {
QByteArray data = reply->readAll();
QJsonParseError documentError; QJsonParseError documentError;
QJsonDocument document = QJsonDocument::fromJson(reply->readAll(), &documentError); QJsonDocument document = QJsonDocument::fromJson(data, &documentError);
LOG_ERROR_X(documentError.error != QJsonParseError::NoError, LOG_ERROR_X(documentError.error != QJsonParseError::NoError,
"NewestPacksPaginator::getDocument", "NewestPacksPaginator::getDocument",