From d523b297f3a4c53a28b7ab7002508eb964cebfbf Mon Sep 17 00:00:00 2001 From: Digital Artifex <7929434+DigitalArtifex@users.noreply.github.com> Date: Wed, 17 Jun 2026 14:52:15 -0400 Subject: [PATCH] Added featured images model, paginator and cache --- KomplexHubContent/pages/HomePage.qml | 16 +- KomplexHubPlugin/CMakeLists.txt | 2 + KomplexHubPlugin/common/data.json | 337 ++++++++++++++++++ KomplexHubPlugin/common/imagecache.h | 48 +++ KomplexHubPlugin/featuredimagesmodel.cpp | 76 ++-- KomplexHubPlugin/featuredimagesmodel.h | 52 +-- .../paginators/featuredimagespaginator.h | 6 +- KomplexHubPlugin/paginators/imagepaginator.h | 317 ++++++++++++++++ .../paginators/wallpaperpaginator.h | 11 +- 9 files changed, 793 insertions(+), 72 deletions(-) create mode 100644 KomplexHubPlugin/common/data.json create mode 100644 KomplexHubPlugin/common/imagecache.h create mode 100644 KomplexHubPlugin/paginators/imagepaginator.h diff --git a/KomplexHubContent/pages/HomePage.qml b/KomplexHubContent/pages/HomePage.qml index 1a10d22..93e0de8 100644 --- a/KomplexHubContent/pages/HomePage.qml +++ b/KomplexHubContent/pages/HomePage.qml @@ -120,7 +120,6 @@ Item { delegate: Item { width: 256 height: 256 - required property string name required property string author required property string description required property string uuid @@ -130,8 +129,8 @@ Item { SearchResultItem { anchors.fill: parent - title: parent.name - author: parent.author + title: parent.author + author: qsTr("Pexels Images") description: parent.description thumbnail: parent.thumbnail uuid: parent.uuid @@ -176,17 +175,18 @@ Item { delegate: Item { width: 256 height: 256 - required property string name - required property string author - required property string description required property string uuid - required property string thumbnail + required property string author required property string authorId + required property string authorUrl + required property string description + required property string thumbnail + required property string original SearchResultItem { anchors.fill: parent - title: parent.name + title: "Pexels Photo" author: parent.author description: parent.description thumbnail: parent.thumbnail diff --git a/KomplexHubPlugin/CMakeLists.txt b/KomplexHubPlugin/CMakeLists.txt index 6b71ef3..7aa4c3a 100644 --- a/KomplexHubPlugin/CMakeLists.txt +++ b/KomplexHubPlugin/CMakeLists.txt @@ -46,6 +46,8 @@ qt_add_qml_module( SOURCES featuredvideosmodel.h SOURCES featuredvideosmodel.cpp SOURCES paginators/featuredvideospaginator.h + SOURCES common/imagecache.h + SOURCES paginators/imagepaginator.h ) diff --git a/KomplexHubPlugin/common/data.json b/KomplexHubPlugin/common/data.json new file mode 100644 index 0000000..5bc4215 --- /dev/null +++ b/KomplexHubPlugin/common/data.json @@ -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 +} \ No newline at end of file diff --git a/KomplexHubPlugin/common/imagecache.h b/KomplexHubPlugin/common/imagecache.h new file mode 100644 index 0000000..65da3ea --- /dev/null +++ b/KomplexHubPlugin/common/imagecache.h @@ -0,0 +1,48 @@ +#ifndef IMAGECACHE_H +#define IMAGECACHE_H + +#include "komplex_global.h" +#include +#include +#include + +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 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 diff --git a/KomplexHubPlugin/featuredimagesmodel.cpp b/KomplexHubPlugin/featuredimagesmodel.cpp index 1d3ea1c..6460c34 100644 --- a/KomplexHubPlugin/featuredimagesmodel.cpp +++ b/KomplexHubPlugin/featuredimagesmodel.cpp @@ -59,45 +59,51 @@ auto FeaturedImagesModel::data(const QModelIndex &index, int role) const -> QVar return {}; } - WallpaperCache dataPoint = m_data.at(index.row()); + ImageCache 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; + 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; @@ -160,7 +166,7 @@ auto FeaturedImagesModel::resetDataModel() -> void beginInsertRows(QModelIndex(), 0, m_paginator->count() - 1); 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); } diff --git a/KomplexHubPlugin/featuredimagesmodel.h b/KomplexHubPlugin/featuredimagesmodel.h index f96542b..0950291 100644 --- a/KomplexHubPlugin/featuredimagesmodel.h +++ b/KomplexHubPlugin/featuredimagesmodel.h @@ -65,14 +65,16 @@ public: UuidRole = Qt::UserRole + 1, AuthorRole, AuthorIdRole, + AuthorUrlRole, DescriptionRole, - NameRole, ThumbnailRole, - CreatedDateRole, - PriceRole, - CurrencyRole, - DownloadCountRole, - TypeRole + PortraitUrlRole, + LandscapeUrlRole, + SmallUrlRole, + OriginalUrlRole, + MediumUrlRole, + LargeUrlRole, + ExtraLargeUrlRole }; Q_ENUM(DataRole) @@ -251,36 +253,44 @@ private: QByteArray("authorId") }, { - static_cast(DescriptionRole), - QByteArray("description") + static_cast(AuthorUrlRole), + QByteArray("authorUrl") }, { - static_cast(NameRole), - QByteArray("name") + static_cast(DescriptionRole), + QByteArray("description") }, { static_cast(ThumbnailRole), QByteArray("thumbnail") }, { - static_cast(CreatedDateRole), - QByteArray("createdDate") + static_cast(PortraitUrlRole), + QByteArray("portrait") }, { - static_cast(PriceRole), - QByteArray("price") + static_cast(LandscapeUrlRole), + QByteArray("landscape") }, { - static_cast(CurrencyRole), - QByteArray("currency") + static_cast(SmallUrlRole), + QByteArray("small") }, { - static_cast(DownloadCountRole), - QByteArray("downloadCount") + static_cast(OriginalUrlRole), + QByteArray("original") }, { - static_cast(TypeRole), - QByteArray("type") + static_cast(MediumUrlRole), + QByteArray("medium") + }, + { + static_cast(LargeUrlRole), + QByteArray("large") + }, + { + static_cast(ExtraLargeUrlRole), + QByteArray("extraLarge") } }; State m_state = Idle; @@ -289,7 +299,7 @@ private: FeaturedImagesPaginator *m_paginator = nullptr; - mutable QList m_data; + 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) diff --git a/KomplexHubPlugin/paginators/featuredimagespaginator.h b/KomplexHubPlugin/paginators/featuredimagespaginator.h index 187f363..f3490f4 100644 --- a/KomplexHubPlugin/paginators/featuredimagespaginator.h +++ b/KomplexHubPlugin/paginators/featuredimagespaginator.h @@ -29,12 +29,12 @@ #include #include -#include "wallpaperpaginator.h" +#include "imagepaginator.h" -class KOMPLEX_EXPORT FeaturedImagesPaginator : public WallpaperPaginator +class KOMPLEX_EXPORT FeaturedImagesPaginator : public ImagePaginator { public: - explicit FeaturedImagesPaginator(QObject *parent = nullptr) : WallpaperPaginator(parent) + explicit FeaturedImagesPaginator(QObject *parent = nullptr) : ImagePaginator(parent) { setUri( QString::fromUtf8(KOMPLEX_ENDPOINT_IMAGES_FEATURED) diff --git a/KomplexHubPlugin/paginators/imagepaginator.h b/KomplexHubPlugin/paginators/imagepaginator.h new file mode 100644 index 0000000..2f63891 --- /dev/null +++ b/KomplexHubPlugin/paginators/imagepaginator.h @@ -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 + */ +#ifndef IMAGEPAGINATOR_H +#define IMAGEPAGINATOR_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +{ + Q_OBJECT +public: + explicit ImagePaginator(QObject *parent = nullptr) : Paginator(parent) + { + SlidingCacheController *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 + */ + [[nodiscard]] + auto fetch( + qsizetype offset, + qsizetype limit + ) const -> FetchResult + { + 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 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 sourceMap; + + QMapIterator 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 result + { + static_cast(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 managerReference = CoreServices::networkAccessManager(); + + QSharedPointer 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 diff --git a/KomplexHubPlugin/paginators/wallpaperpaginator.h b/KomplexHubPlugin/paginators/wallpaperpaginator.h index 0aff605..de2cedf 100644 --- a/KomplexHubPlugin/paginators/wallpaperpaginator.h +++ b/KomplexHubPlugin/paginators/wallpaperpaginator.h @@ -138,19 +138,19 @@ protected: QHttpHeaders::fromMultiMap( { { - QByteArray("Query"), + QByteArray("query"), m_query.toUtf8() }, { - QByteArray("Offset"), + QByteArray("offset"), QString::number(offset).toUtf8() }, { - QByteArray("Limit"), + QByteArray("limit"), QString::number(limit).toUtf8() }, { - QByteArray("SessionToken"), + QByteArray("session_token"), CoreServices::sessionToken() } } @@ -266,8 +266,9 @@ protected: [[nodiscard]] auto getDocument(QNetworkReply *reply) const -> QJsonDocument { + QByteArray data = reply->readAll(); QJsonParseError documentError; - QJsonDocument document = QJsonDocument::fromJson(reply->readAll(), &documentError); + QJsonDocument document = QJsonDocument::fromJson(data, &documentError); LOG_ERROR_X(documentError.error != QJsonParseError::NoError, "NewestPacksPaginator::getDocument",