From 881b2f2b385f19d9b64a149fca3c3741a803a172 Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 2 Mar 2022 18:35:59 -0300 Subject: refactor: Use a single indexed pack for mods Since there's little difference between them, let's remove duplication and merge them. --- launcher/modplatform/ModIndex.h | 42 +++++++++++++ launcher/modplatform/flame/FlameModIndex.cpp | 62 +++++++++---------- launcher/modplatform/flame/FlameModIndex.h | 48 +++------------ .../modplatform/modrinth/ModrinthPackIndex.cpp | 69 +++++++++++----------- launcher/modplatform/modrinth/ModrinthPackIndex.h | 48 +++------------ .../ui/pages/modplatform/flame/FlameModModel.cpp | 6 +- .../ui/pages/modplatform/flame/FlameModModel.h | 2 +- .../ui/pages/modplatform/flame/FlameModPage.cpp | 6 +- launcher/ui/pages/modplatform/flame/FlameModPage.h | 2 +- .../pages/modplatform/modrinth/ModrinthModel.cpp | 6 +- .../ui/pages/modplatform/modrinth/ModrinthModel.h | 2 +- .../ui/pages/modplatform/modrinth/ModrinthPage.cpp | 8 +-- .../ui/pages/modplatform/modrinth/ModrinthPage.h | 2 +- 13 files changed, 137 insertions(+), 166 deletions(-) create mode 100644 launcher/modplatform/ModIndex.h (limited to 'launcher') diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h new file mode 100644 index 00000000..7e1cf254 --- /dev/null +++ b/launcher/modplatform/ModIndex.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace ModPlatform { + +struct ModpackAuthor { + QString name; + QString url; +}; + +struct IndexedVersion { + QVariant addonId; + QVariant fileId; + QString version; + QVector mcVersion; + QString downloadUrl; + QString date; + QString fileName; + QVector loaders = {}; +}; + +struct IndexedPack { + QVariant addonId; + QString name; + QString description; + QList authors; + QString logoName; + QString logoUrl; + QString websiteUrl; + + bool versionsLoaded = false; + QVector versions; +}; + +} // namespace ModPlatform + +Q_DECLARE_METATYPE(ModPlatform::IndexedPack) diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index 4adaf5f1..61cb534c 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -1,13 +1,11 @@ -#include #include "FlameModIndex.h" + #include "Json.h" -#include "net/NetJob.h" -#include "BaseInstance.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" +#include "net/NetJob.h" - -void FlameMod::loadIndexedPack(FlameMod::IndexedPack & pack, QJsonObject & obj) +void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) { pack.addonId = Json::requireInteger(obj, "id"); pack.name = Json::requireString(obj, "name"); @@ -16,10 +14,10 @@ void FlameMod::loadIndexedPack(FlameMod::IndexedPack & pack, QJsonObject & obj) bool thumbnailFound = false; auto attachments = Json::requireArray(obj, "attachments"); - for(auto attachmentRaw: attachments) { + for (auto attachmentRaw : attachments) { auto attachmentObj = Json::requireObject(attachmentRaw); bool isDefault = attachmentObj.value("isDefault").toBool(false); - if(isDefault) { + if (isDefault) { thumbnailFound = true; pack.logoName = Json::requireString(attachmentObj, "title"); pack.logoUrl = Json::requireString(attachmentObj, "thumbnailUrl"); @@ -27,37 +25,35 @@ void FlameMod::loadIndexedPack(FlameMod::IndexedPack & pack, QJsonObject & obj) } } - if(!thumbnailFound) { - throw JSONValidationError(QString("Pack without an icon, skipping: %1").arg(pack.name)); - } - + if (!thumbnailFound) { throw JSONValidationError(QString("Pack without an icon, skipping: %1").arg(pack.name)); } auto authors = Json::requireArray(obj, "authors"); - for(auto authorIter: authors) { + for (auto authorIter : authors) { auto author = Json::requireObject(authorIter); - FlameMod::ModpackAuthor packAuthor; + ModPlatform::ModpackAuthor packAuthor; packAuthor.name = Json::requireString(author, "name"); packAuthor.url = Json::requireString(author, "url"); pack.authors.append(packAuthor); } } -void FlameMod::loadIndexedPackVersions(FlameMod::IndexedPack & pack, QJsonArray & arr, const shared_qobject_ptr& network, BaseInstance * inst) +void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, + QJsonArray& arr, + const shared_qobject_ptr& network, + BaseInstance* inst) { - QVector unsortedVersions; - bool hasFabric = !((MinecraftInstance *)inst)->getPackProfile()->getComponentVersion("net.fabricmc.fabric-loader").isEmpty(); - QString mcVersion = ((MinecraftInstance *)inst)->getPackProfile()->getComponentVersion("net.minecraft"); + QVector unsortedVersions; + bool hasFabric = !((MinecraftInstance*)inst)->getPackProfile()->getComponentVersion("net.fabricmc.fabric-loader").isEmpty(); + QString mcVersion = ((MinecraftInstance*)inst)->getPackProfile()->getComponentVersion("net.minecraft"); - for(auto versionIter: arr) { + for (auto versionIter : arr) { auto obj = versionIter.toObject(); auto versionArray = Json::requireArray(obj, "gameVersion"); - if (versionArray.isEmpty()) { - continue; - } + if (versionArray.isEmpty()) { continue; } - FlameMod::IndexedVersion file; - for(auto mcVer : versionArray){ + ModPlatform::IndexedVersion file; + for (auto mcVer : versionArray) { file.mcVersion.append(mcVer.toString()); } @@ -70,29 +66,27 @@ void FlameMod::loadIndexedPackVersions(FlameMod::IndexedPack & pack, QJsonArray auto modules = Json::requireArray(obj, "modules"); bool is_valid_fabric_version = false; - for(auto m : modules){ - auto fname = Json::requireString(m.toObject(),"foldername"); + for (auto m : modules) { + auto fname = Json::requireString(m.toObject(), "foldername"); // FIXME: This does not work properly when a mod supports more than one mod loader, since // they bundle the meta files for all of them in the same arquive, even when that version // doesn't support the given mod loader. - if(hasFabric){ - if(fname == "fabric.mod.json"){ + if (hasFabric) { + if (fname == "fabric.mod.json") { is_valid_fabric_version = true; break; } - } - else break; + } else + break; // NOTE: Since we're not validating forge versions, we can just skip this loop. } - if(hasFabric && !is_valid_fabric_version) - continue; + if (hasFabric && !is_valid_fabric_version) continue; unsortedVersions.append(file); } - auto orderSortPredicate = [](const IndexedVersion & a, const IndexedVersion & b) -> bool - { - //dates are in RFC 3339 format + auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { + // dates are in RFC 3339 format return a.date > b.date; }; std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate); diff --git a/launcher/modplatform/flame/FlameModIndex.h b/launcher/modplatform/flame/FlameModIndex.h index 0293bb23..34f71498 100644 --- a/launcher/modplatform/flame/FlameModIndex.h +++ b/launcher/modplatform/flame/FlameModIndex.h @@ -3,48 +3,18 @@ // #pragma once -#include -#include -#include -#include + +#include "modplatform/ModIndex.h" + #include -#include -#include "net/NetJob.h" #include "BaseInstance.h" namespace FlameMod { - struct ModpackAuthor { - QString name; - QString url; - }; - - struct IndexedVersion { - int addonId; - int fileId; - QString version; - QVector mcVersion; - QString downloadUrl; - QString date; - QString fileName; - }; - - struct IndexedPack - { - int addonId; - QString name; - QString description; - QList authors; - QString logoName; - QString logoUrl; - QString websiteUrl; - - bool versionsLoaded = false; - QVector versions; - }; - - void loadIndexedPack(IndexedPack & m, QJsonObject & obj); - void loadIndexedPackVersions(IndexedPack &pack, QJsonArray &arr, const shared_qobject_ptr &network, BaseInstance *inst); -} +void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj); +void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, + QJsonArray& arr, + const shared_qobject_ptr& network, + BaseInstance* inst); -Q_DECLARE_METATYPE(FlameMod::IndexedPack) +} // namespace FlameMod diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 9017eb67..a59186f7 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -1,14 +1,11 @@ -#include #include "ModrinthPackIndex.h" #include "Json.h" -#include "net/NetJob.h" -#include "BaseInstance.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" +#include "net/NetJob.h" - -void Modrinth::loadIndexedPack(Modrinth::IndexedPack & pack, QJsonObject & obj) +void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) { pack.addonId = Json::requireString(obj, "project_id"); pack.name = Json::requireString(obj, "title"); @@ -16,59 +13,60 @@ void Modrinth::loadIndexedPack(Modrinth::IndexedPack & pack, QJsonObject & obj) pack.description = Json::ensureString(obj, "description", ""); pack.logoUrl = Json::requireString(obj, "icon_url"); - pack.logoName = pack.addonId; + pack.logoName = pack.addonId.toString(); - Modrinth::ModpackAuthor modAuthor; + ModPlatform::ModpackAuthor modAuthor; modAuthor.name = Json::requireString(obj, "author"); - modAuthor.url = "https://modrinth.com/user/"+modAuthor.name; - pack.author = modAuthor; + modAuthor.url = "https://modrinth.com/user/" + modAuthor.name; + pack.authors.append(modAuthor); } -void Modrinth::loadIndexedPackVersions(Modrinth::IndexedPack & pack, QJsonArray & arr, const shared_qobject_ptr& network, BaseInstance * inst) +void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, + QJsonArray& arr, + const shared_qobject_ptr& network, + BaseInstance* inst) { - QVector unsortedVersions; - bool hasFabric = !((MinecraftInstance *)inst)->getPackProfile()->getComponentVersion("net.fabricmc.fabric-loader").isEmpty(); - QString mcVersion = ((MinecraftInstance *)inst)->getPackProfile()->getComponentVersion("net.minecraft"); + QVector unsortedVersions; + bool hasFabric = !((MinecraftInstance*)inst)->getPackProfile()->getComponentVersion("net.fabricmc.fabric-loader").isEmpty(); + QString mcVersion = ((MinecraftInstance*)inst)->getPackProfile()->getComponentVersion("net.minecraft"); - for(auto versionIter: arr) { + for (auto versionIter : arr) { auto obj = versionIter.toObject(); - Modrinth::IndexedVersion file; - file.addonId = Json::requireString(obj,"project_id") ; + ModPlatform::IndexedVersion file; + file.addonId = Json::requireString(obj, "project_id"); file.fileId = Json::requireString(obj, "id"); file.date = Json::requireString(obj, "date_published"); auto versionArray = Json::requireArray(obj, "game_versions"); - if (versionArray.empty()) { - continue; - } - for(auto mcVer : versionArray){ + if (versionArray.empty()) { continue; } + for (auto mcVer : versionArray) { file.mcVersion.append(mcVer.toString()); } - auto loaders = Json::requireArray(obj,"loaders"); - for(auto loader : loaders){ + auto loaders = Json::requireArray(obj, "loaders"); + for (auto loader : loaders) { file.loaders.append(loader.toString()); } file.version = Json::requireString(obj, "name"); auto files = Json::requireArray(obj, "files"); int i = 0; - while (files.count() > 1 && i < files.count()){ - //try to resolve the correct file + while (files.count() > 1 && i < files.count()) { + // try to resolve the correct file auto parent = files[i].toObject(); auto fileName = Json::requireString(parent, "filename"); - //avoid grabbing "dev" files - if(fileName.contains("javadocs",Qt::CaseInsensitive) || fileName.contains("sources",Qt::CaseInsensitive)){ + // avoid grabbing "dev" files + if (fileName.contains("javadocs", Qt::CaseInsensitive) || fileName.contains("sources", Qt::CaseInsensitive)) { i++; continue; } - //grab the correct mod loader - if(fileName.contains("forge",Qt::CaseInsensitive) || fileName.contains("fabric",Qt::CaseInsensitive) ){ - if(hasFabric){ - if(fileName.contains("forge",Qt::CaseInsensitive)){ + // grab the correct mod loader + if (fileName.contains("forge", Qt::CaseInsensitive) || fileName.contains("fabric", Qt::CaseInsensitive)) { + if (hasFabric) { + if (fileName.contains("forge", Qt::CaseInsensitive)) { i++; continue; } - }else{ - if(fileName.contains("fabric",Qt::CaseInsensitive)){ + } else { + if (fileName.contains("fabric", Qt::CaseInsensitive)) { i++; continue; } @@ -77,16 +75,15 @@ void Modrinth::loadIndexedPackVersions(Modrinth::IndexedPack & pack, QJsonArray break; } auto parent = files[i].toObject(); - if(parent.contains("url")) { + if (parent.contains("url")) { file.downloadUrl = Json::requireString(parent, "url"); file.fileName = Json::requireString(parent, "filename"); unsortedVersions.append(file); } } - auto orderSortPredicate = [](const IndexedVersion & a, const IndexedVersion & b) -> bool - { - //dates are in RFC 3339 format + auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { + // dates are in RFC 3339 format return a.date > b.date; }; std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate); diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h index 3a4cd270..abfdabb6 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.h +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h @@ -1,48 +1,16 @@ #pragma once -#include -#include -#include -#include +#include "modplatform/ModIndex.h" + #include -#include -#include "net/NetJob.h" #include "BaseInstance.h" namespace Modrinth { -struct ModpackAuthor { - QString name; - QString url; -}; - -struct IndexedVersion { - QString addonId; - QString fileId; - QString version; - QVector mcVersion; - QString downloadUrl; - QString date; - QString fileName; - QVector loaders; -}; - -struct IndexedPack -{ - QString addonId; - QString name; - QString description; - ModpackAuthor author; - QString logoName; - QString logoUrl; - QString websiteUrl; - - bool versionsLoaded = false; - QVector versions; -}; - -void loadIndexedPack(IndexedPack & m, QJsonObject & obj); -void loadIndexedPackVersions(IndexedPack &pack, QJsonArray &arr, const shared_qobject_ptr &network, BaseInstance *inst); -} +void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj); +void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, + QJsonArray& arr, + const shared_qobject_ptr& network, + BaseInstance* inst); -Q_DECLARE_METATYPE(Modrinth::IndexedPack) +} // namespace Modrinth diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp index e8afba5a..052f30d1 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp @@ -39,7 +39,7 @@ QVariant ListModel::data(const QModelIndex &index, int role) const return QString("INVALID INDEX %1").arg(pos); } - IndexedPack pack = modpacks.at(pos); + ModPlatform::IndexedPack pack = modpacks.at(pos); if(role == Qt::DisplayRole) { return pack.name; @@ -225,12 +225,12 @@ void ListModel::searchRequestFinished() return; } - QList newList; + QList newList; auto packs = doc.array(); for(auto packRaw : packs) { auto packObj = packRaw.toObject(); - FlameMod::IndexedPack pack; + ModPlatform::IndexedPack pack; try { FlameMod::loadIndexedPack(pack, packObj); diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.h b/launcher/ui/pages/modplatform/flame/FlameModModel.h index 0c1cb95e..ec19ab2f 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModModel.h +++ b/launcher/ui/pages/modplatform/flame/FlameModModel.h @@ -57,7 +57,7 @@ private: void requestLogo(QString file, QString url); private: - QList modpacks; + QList modpacks; QStringList m_failedLogos; QStringList m_loadingLogos; LogoMap m_logoMap; diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp index 114ac907..48d38b82 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp @@ -77,7 +77,7 @@ void FlameModPage::onSelectionChanged(QModelIndex first, QModelIndex second) { return; } - current = listModel->data(first, Qt::UserRole).value(); + current = listModel->data(first, Qt::UserRole).value(); QString text = ""; QString name = current.name; @@ -86,7 +86,7 @@ void FlameModPage::onSelectionChanged(QModelIndex first, QModelIndex second) { else text = "" + name + ""; if (!current.authors.empty()) { - auto authorToStr = [](FlameMod::ModpackAuthor &author) { + auto authorToStr = [](ModPlatform::ModpackAuthor &author) { if (author.url.isEmpty()) { return author.name; } @@ -112,7 +112,7 @@ void FlameModPage::onSelectionChanged(QModelIndex first, QModelIndex second) { new NetJob(QString("Flame::ModVersions(%1)").arg(current.name), APPLICATION->network()); auto response = new QByteArray(); - int addonId = current.addonId; + int addonId = current.addonId.toInt(); netJob->addNetAction(Net::Download::makeByteArray( QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files") .arg(addonId), diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.h b/launcher/ui/pages/modplatform/flame/FlameModPage.h index b5b19a4f..24dfcbc7 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.h +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.h @@ -62,7 +62,7 @@ private: Ui::FlameModPage *ui = nullptr; ModDownloadDialog* dialog = nullptr; FlameMod::ListModel* listModel = nullptr; - FlameMod::IndexedPack current; + ModPlatform::IndexedPack current; int selectedVersion = -1; }; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 5a18830a..088ca411 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -41,7 +41,7 @@ QVariant ListModel::data(const QModelIndex &index, int role) const return QString("INVALID INDEX %1").arg(pos); } - IndexedPack pack = modpacks.at(pos); + ModPlatform::IndexedPack pack = modpacks.at(pos); if(role == Qt::DisplayRole) { return pack.name; @@ -222,12 +222,12 @@ void Modrinth::ListModel::searchRequestFinished() return; } - QList newList; + QList newList; auto packs = doc.object().value("hits").toArray(); for(auto packRaw : packs) { auto packObj = packRaw.toObject(); - Modrinth::IndexedPack pack; + ModPlatform::IndexedPack pack; try { Modrinth::loadIndexedPack(pack, packObj); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h index 53f1f134..20661412 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h @@ -57,7 +57,7 @@ private: void requestLogo(QString file, QString url); private: - QList modpacks; + QList modpacks; QStringList m_failedLogos; QStringList m_loadingLogos; LogoMap m_logoMap; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index 35cd743a..4dfc8504 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -76,7 +76,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) { return; } - current = listModel->data(first, Qt::UserRole).value(); + current = listModel->data(first, Qt::UserRole).value(); QString text = ""; QString name = current.name; @@ -84,8 +84,8 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) { text = name; else text = "" + name + ""; - text += "
" + tr(" by ") + "" + - current.author.name + "

"; + text += "
" + tr(" by ") + "" + + current.authors[0].name + "

"; ui->packDescription->setHtml(text + current.description); if (!current.versionsLoaded) { @@ -98,7 +98,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) { new NetJob(QString("Modrinth::ModVersions(%1)").arg(current.name), APPLICATION->network()); auto response = new QByteArray(); - QString addonId = current.addonId; + QString addonId = current.addonId.toString(); netJob->addNetAction(Net::Download::makeByteArray( QString("https://api.modrinth.com/v2/project/%1/version").arg(addonId), response)); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h index 52b538e3..1d725d47 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h @@ -62,7 +62,7 @@ private: Ui::ModrinthPage *ui = nullptr; ModDownloadDialog* dialog = nullptr; Modrinth::ListModel* listModel = nullptr; - Modrinth::IndexedPack current; + ModPlatform::IndexedPack current; int selectedVersion = -1; }; -- cgit From 0dd1c26cf3cd68cd83f5d9da6cf34d4aa3f30db2 Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 2 Mar 2022 21:17:10 -0300 Subject: refactor: extract common code in mod pages and model This creates a hierarchy in which ModPage and ModModel are the parents of every mod provider, providing the basic functionality common to all of them. It also imposes a unique .ui file (they were already equal before, just duplicated basically) on all mod providers. --- launcher/CMakeLists.txt | 8 +- launcher/ui/pages/modplatform/ModModel.cpp | 166 ++++++++++++++ launcher/ui/pages/modplatform/ModModel.h | 60 +++++ launcher/ui/pages/modplatform/ModPage.cpp | 152 +++++++++++++ launcher/ui/pages/modplatform/ModPage.h | 57 +++++ launcher/ui/pages/modplatform/ModPage.ui | 97 ++++++++ .../ui/pages/modplatform/flame/FlameModModel.cpp | 223 ++----------------- .../ui/pages/modplatform/flame/FlameModModel.h | 68 ++---- .../ui/pages/modplatform/flame/FlameModPage.cpp | 231 ++++--------------- launcher/ui/pages/modplatform/flame/FlameModPage.h | 72 ++---- .../ui/pages/modplatform/flame/FlameModPage.ui | 97 -------- .../pages/modplatform/modrinth/ModrinthModel.cpp | 245 +++------------------ .../ui/pages/modplatform/modrinth/ModrinthModel.h | 68 ++---- .../ui/pages/modplatform/modrinth/ModrinthPage.cpp | 217 ++++-------------- .../ui/pages/modplatform/modrinth/ModrinthPage.h | 72 ++---- .../ui/pages/modplatform/modrinth/ModrinthPage.ui | 97 -------- 16 files changed, 720 insertions(+), 1210 deletions(-) create mode 100644 launcher/ui/pages/modplatform/ModModel.cpp create mode 100644 launcher/ui/pages/modplatform/ModModel.h create mode 100644 launcher/ui/pages/modplatform/ModPage.cpp create mode 100644 launcher/ui/pages/modplatform/ModPage.h create mode 100644 launcher/ui/pages/modplatform/ModPage.ui delete mode 100644 launcher/ui/pages/modplatform/flame/FlameModPage.ui delete mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui (limited to 'launcher') diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 86c05651..0dcda925 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -721,6 +721,11 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/VanillaPage.cpp ui/pages/modplatform/VanillaPage.h + ui/pages/modplatform/ModPage.cpp + ui/pages/modplatform/ModPage.h + ui/pages/modplatform/ModModel.cpp + ui/pages/modplatform/ModModel.h + ui/pages/modplatform/atlauncher/AtlFilterModel.cpp ui/pages/modplatform/atlauncher/AtlFilterModel.h ui/pages/modplatform/atlauncher/AtlListModel.cpp @@ -879,13 +884,12 @@ qt5_wrap_ui(LAUNCHER_UI ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui ui/pages/modplatform/atlauncher/AtlPage.ui ui/pages/modplatform/VanillaPage.ui + ui/pages/modplatform/ModPage.ui ui/pages/modplatform/flame/FlamePage.ui - ui/pages/modplatform/flame/FlameModPage.ui ui/pages/modplatform/legacy_ftb/Page.ui ui/pages/modplatform/ImportPage.ui ui/pages/modplatform/ftb/FtbPage.ui ui/pages/modplatform/technic/TechnicPage.ui - ui/pages/modplatform/modrinth/ModrinthPage.ui ui/widgets/InstanceCardWidget.ui ui/widgets/CustomCommands.ui ui/widgets/MCModInfoFrame.ui diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp new file mode 100644 index 00000000..5bd7e33e --- /dev/null +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -0,0 +1,166 @@ +#include "ModModel.h" +#include "ModPage.h" + +#include "ui/dialogs/ModDownloadDialog.h" + +#include + +namespace ModPlatform { + +ListModel::ListModel(ModPage* parent) : QAbstractListModel(parent), m_parent(parent) {} + +ListModel::~ListModel() {} + +int ListModel::rowCount(const QModelIndex& parent) const +{ + return modpacks.size(); +} + +int ListModel::columnCount(const QModelIndex& parent) const +{ + return 1; +} + +QVariant ListModel::data(const QModelIndex& index, int role) const +{ + int pos = index.row(); + if (pos >= modpacks.size() || pos < 0 || !index.isValid()) { return QString("INVALID INDEX %1").arg(pos); } + + ModPlatform::IndexedPack pack = modpacks.at(pos); + if (role == Qt::DisplayRole) { + return pack.name; + } else if (role == Qt::ToolTipRole) { + if (pack.description.length() > 100) { + // some magic to prevent to long tooltips and replace html linebreaks + QString edit = pack.description.left(97); + edit = edit.left(edit.lastIndexOf("
")).left(edit.lastIndexOf(" ")).append("..."); + return edit; + } + return pack.description; + } else if (role == Qt::DecorationRole) { + if (m_logoMap.contains(pack.logoName)) { return (m_logoMap.value(pack.logoName)); } + QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); + ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl); + return icon; + } else if (role == Qt::UserRole) { + QVariant v; + v.setValue(pack); + return v; + } + + return QVariant(); +} + +void ListModel::logoLoaded(QString logo, QIcon out) +{ + m_loadingLogos.removeAll(logo); + m_logoMap.insert(logo, out); + for (int i = 0; i < modpacks.size(); i++) { + if (modpacks[i].logoName == logo) { emit dataChanged(createIndex(i, 0), createIndex(i, 0), { Qt::DecorationRole }); } + } +} + +void ListModel::logoFailed(QString logo) +{ + m_failedLogos.append(logo); + m_loadingLogos.removeAll(logo); +} + +Qt::ItemFlags ListModel::flags(const QModelIndex& index) const +{ + return QAbstractListModel::flags(index); +} + +bool ListModel::canFetchMore(const QModelIndex& parent) const +{ + return searchState == CanPossiblyFetchMore; +} + +void ListModel::fetchMore(const QModelIndex& parent) +{ + if (parent.isValid()) return; + if (nextSearchOffset == 0) { + qWarning() << "fetchMore with 0 offset is wrong..."; + return; + } + performPaginatedSearch(); +} + +void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback) +{ + if (m_logoMap.contains(logo)) { + callback(APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath()); + } else { + requestLogo(logo, logoUrl); + } +} + +void ListModel::searchWithTerm(const QString& term, const int sort) +{ + if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) { return; } + currentSearchTerm = term; + currentSort = sort; + if (jobPtr) { + jobPtr->abort(); + searchState = ResetRequested; + return; + } else { + beginResetModel(); + modpacks.clear(); + endResetModel(); + searchState = None; + } + nextSearchOffset = 0; + performPaginatedSearch(); +} + +void ListModel::searchRequestFailed(QString reason) +{ + if (jobPtr->first()->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 409) { + // 409 Gone, notify user to update + QMessageBox::critical(nullptr, tr("Error"), + QString("%1 %2") + .arg(m_parent->displayName()) + .arg(tr("API version too old!\nPlease update PolyMC!"))); + // self-destruct + ((ModDownloadDialog*)((ModPage*)parent())->parentWidget())->reject(); + } + jobPtr.reset(); + + if (searchState == ResetRequested) { + beginResetModel(); + modpacks.clear(); + endResetModel(); + + nextSearchOffset = 0; + performPaginatedSearch(); + } else { + searchState = Finished; + } +} + +void ListModel::requestLogo(QString logo, QString url) +{ + if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo)) { return; } + + MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0))); + auto job = new NetJob(QString("%1 Icon Download %2").arg(m_parent->debugName()).arg(logo), APPLICATION->network()); + job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); + + auto fullPath = entry->getFullPath(); + QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] { + job->deleteLater(); + emit logoLoaded(logo, QIcon(fullPath)); + if (waitingCallbacks.contains(logo)) { waitingCallbacks.value(logo)(fullPath); } + }); + + QObject::connect(job, &NetJob::failed, this, [this, logo, job] { + job->deleteLater(); + emit logoFailed(logo); + }); + + job->start(); + m_loadingLogos.append(logo); +} + +} // namespace ModPlatform diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h new file mode 100644 index 00000000..ae8ceb7c --- /dev/null +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -0,0 +1,60 @@ +#pragma once + +#include + +#include "modplatform/ModIndex.h" +#include "net/NetJob.h" + +class ModPage; + +namespace ModPlatform { + +typedef QMap LogoMap; +typedef std::function LogoCallback; + +class ListModel : public QAbstractListModel { + Q_OBJECT + + public: + ListModel(ModPage* parent); + virtual ~ListModel(); + + int rowCount(const QModelIndex& parent) const override; + int columnCount(const QModelIndex& parent) const override; + QVariant data(const QModelIndex& index, int role) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + bool canFetchMore(const QModelIndex& parent) const override; + void fetchMore(const QModelIndex& parent) override; + + void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); + void searchWithTerm(const QString& term, const int sort); + + protected slots: + virtual void performPaginatedSearch() = 0; + virtual void searchRequestFinished() = 0; + + void logoFailed(QString logo); + void logoLoaded(QString logo, QIcon out); + + void searchRequestFailed(QString reason); + + protected: + void requestLogo(QString file, QString url); + + protected: + ModPage* m_parent; + + QList modpacks; + QStringList m_failedLogos; + QStringList m_loadingLogos; + LogoMap m_logoMap; + QMap waitingCallbacks; + + QString currentSearchTerm; + int currentSort = 0; + int nextSearchOffset = 0; + enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None; + NetJob::Ptr jobPtr; + QByteArray response; +}; +} // namespace ModPlatform diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp new file mode 100644 index 00000000..8af534a6 --- /dev/null +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -0,0 +1,152 @@ +#include "ModPage.h" +#include "ui_ModPage.h" + +#include + +#include "ui/dialogs/ModDownloadDialog.h" + +ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance) + : QWidget(dialog), m_instance(instance), ui(new Ui::ModPage), dialog(dialog) +{ + ui->setupUi(this); + connect(ui->searchButton, &QPushButton::clicked, this, &ModPage::triggerSearch); + ui->searchEdit->installEventFilter(this); + + ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); + +} + +ModPage::~ModPage() +{ + delete ui; +} + +void ModPage::openedImpl() +{ + updateSelectionButton(); + triggerSearch(); +} + +bool ModPage::eventFilter(QObject* watched, QEvent* event) +{ + if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Return) { + triggerSearch(); + keyEvent->accept(); + return true; + } + } + return QWidget::eventFilter(watched, event); +} + +void ModPage::updateSelectionButton() +{ + if (!isOpened || selectedVersion < 0) { + ui->modSelectionButton->setEnabled(false); + return; + } + + ui->modSelectionButton->setEnabled(true); + auto& version = current.versions[selectedVersion]; + if (!dialog->isModSelected(current.name, version.fileName)) { + ui->modSelectionButton->setText(tr("Select mod for download")); + } else { + ui->modSelectionButton->setText(tr("Deselect mod for download")); + } +} + +void ModPage::triggerSearch() +{ + listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex()); +} + +void ModPage::onSelectionChanged(QModelIndex first, QModelIndex second) +{ + ui->versionSelectionBox->clear(); + + if (!first.isValid()) { return; } + + current = listModel->data(first, Qt::UserRole).value(); + QString text = ""; + QString name = current.name; + + if (current.websiteUrl.isEmpty()) + text = name; + else + text = "" + name + ""; + + if (!current.authors.empty()) { + auto authorToStr = [](ModPlatform::ModpackAuthor& author) { + if (author.url.isEmpty()) { return author.name; } + return QString("%2").arg(author.url, author.name); + }; + QStringList authorStrs; + for (auto& author : current.authors) { + authorStrs.push_back(authorToStr(author)); + } + text += "
" + tr(" by ") + authorStrs.join(", "); + } + text += "

"; + + ui->packDescription->setHtml(text + current.description); + + if (!current.versionsLoaded) { + qDebug() << QString("Loading %1 mod versions").arg(debugName()); + + ui->modSelectionButton->setText(tr("Loading versions...")); + ui->modSelectionButton->setEnabled(false); + + auto netJob = new NetJob(QString("%1::ModVersions(%2)").arg(debugName()).arg(current.name), APPLICATION->network()); + auto response = new QByteArray(); + QString addonId = current.addonId.toString(); + //FIXME + if(debugName() == "Modrinth") + netJob->addNetAction( + Net::Download::makeByteArray(QString("https://api.modrinth.com/v2/project/%1/version").arg(addonId), response)); + else + netJob->addNetAction( + Net::Download::makeByteArray(QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files").arg(addonId), response)); + + QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId]{ + onModVersionSucceed(this, response, addonId); + }); + + QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { + netJob->deleteLater(); + delete response; + }); + + netJob->start(); + } else { + for (int i = 0; i < current.versions.size(); i++) { + ui->versionSelectionBox->addItem(current.versions[i].version, QVariant(i)); + } + if (ui->versionSelectionBox->count() == 0) { ui->versionSelectionBox->addItem(tr("No Valid Version found !"), QVariant(-1)); } + + updateSelectionButton(); + } +} + +void ModPage::onVersionSelectionChanged(QString data) +{ + if (data.isNull() || data.isEmpty()) { + selectedVersion = -1; + return; + } + selectedVersion = ui->versionSelectionBox->currentData().toInt(); + updateSelectionButton(); +} + +void ModPage::onModSelected() +{ + auto& version = current.versions[selectedVersion]; + if (dialog->isModSelected(current.name, version.fileName)) { + dialog->removeSelectedMod(current.name); + } else { + dialog->addSelectedMod(current.name, new ModDownloadTask(version.downloadUrl, version.fileName, dialog->mods)); + } + + updateSelectionButton(); +} diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h new file mode 100644 index 00000000..c0102549 --- /dev/null +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +#include "modplatform/ModIndex.h" +#include "tasks/Task.h" +#include "ui/pages/BasePage.h" +#include "ui/pages/modplatform/ModModel.h" + +class ModDownloadDialog; + +namespace Ui { +class ModPage; +} + +class ModPage : public QWidget, public BasePage { + Q_OBJECT + + public: + explicit ModPage(ModDownloadDialog* dialog, BaseInstance* instance); + virtual ~ModPage(); + + inline virtual QString displayName() const override = 0; + inline virtual QIcon icon() const override = 0; + inline virtual QString id() const override = 0; + inline virtual QString helpPage() const override = 0; + + inline virtual QString debugName() const = 0; + inline virtual QString metaEntryBase() const = 0; + + virtual bool shouldDisplay() const override = 0; + + void openedImpl() override; + bool eventFilter(QObject* watched, QEvent* event) override; + + BaseInstance* m_instance; + + protected: + virtual void onModVersionSucceed(ModPage*, QByteArray*, QString) = 0; + + void updateSelectionButton(); + + protected slots: + void triggerSearch(); + void onSelectionChanged(QModelIndex first, QModelIndex second); + void onVersionSelectionChanged(QString data); + void onModSelected(); + + protected: + Ui::ModPage* ui = nullptr; + ModDownloadDialog* dialog = nullptr; + ModPlatform::ListModel* listModel = nullptr; + ModPlatform::IndexedPack current; + + int selectedVersion = -1; +}; diff --git a/launcher/ui/pages/modplatform/ModPage.ui b/launcher/ui/pages/modplatform/ModPage.ui new file mode 100644 index 00000000..2b52df22 --- /dev/null +++ b/launcher/ui/pages/modplatform/ModPage.ui @@ -0,0 +1,97 @@ + + + ModPage + + + + 0 + 0 + 837 + 685 + + + + + + + + + true + + + true + + + + + + + Qt::ScrollBarAlwaysOff + + + true + + + + 48 + 48 + + + + + + + + + + Search + + + + + + + Search and filter ... + + + + + + + + + + + + Version selected: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Select mod for download + + + + + + + + + searchEdit + searchButton + packView + packDescription + sortByBox + versionSelectionBox + + + + diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp index 052f30d1..5ab6672f 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp @@ -1,169 +1,25 @@ #include "FlameModModel.h" -#include "Application.h" +#include "FlameModPage.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" -#include "FlameModPage.h" -#include - -#include -#include - -#include +#include namespace FlameMod { -ListModel::ListModel(FlameModPage *parent) : QAbstractListModel(parent) -{ -} - -ListModel::~ListModel() -{ -} - -int ListModel::rowCount(const QModelIndex &parent) const -{ - return modpacks.size(); -} - -int ListModel::columnCount(const QModelIndex &parent) const -{ - return 1; -} - -QVariant ListModel::data(const QModelIndex &index, int role) const -{ - int pos = index.row(); - if(pos >= modpacks.size() || pos < 0 || !index.isValid()) - { - return QString("INVALID INDEX %1").arg(pos); - } - - ModPlatform::IndexedPack pack = modpacks.at(pos); - if(role == Qt::DisplayRole) - { - return pack.name; - } - else if (role == Qt::ToolTipRole) - { - if(pack.description.length() > 100) - { - //some magic to prevent to long tooltips and replace html linebreaks - QString edit = pack.description.left(97); - edit = edit.left(edit.lastIndexOf("
")).left(edit.lastIndexOf(" ")).append("..."); - return edit; - - } - return pack.description; - } - else if(role == Qt::DecorationRole) - { - if(m_logoMap.contains(pack.logoName)) - { - return (m_logoMap.value(pack.logoName)); - } - QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); - ((ListModel *)this)->requestLogo(pack.logoName, pack.logoUrl); - return icon; - } - else if(role == Qt::UserRole) - { - QVariant v; - v.setValue(pack); - return v; - } - - return QVariant(); -} - -void ListModel::logoLoaded(QString logo, QIcon out) -{ - m_loadingLogos.removeAll(logo); - m_logoMap.insert(logo, out); - for(int i = 0; i < modpacks.size(); i++) { - if(modpacks[i].logoName == logo) { - emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole}); - } - } -} +ListModel::ListModel(FlameModPage* parent) : ModPlatform::ListModel(parent) {} -void ListModel::logoFailed(QString logo) -{ - m_failedLogos.append(logo); - m_loadingLogos.removeAll(logo); -} +ListModel::~ListModel() {} -void ListModel::requestLogo(QString logo, QString url) -{ - if(m_loadingLogos.contains(logo) || m_failedLogos.contains(logo)) - { - return; - } - - MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FlameMods", QString("logos/%1").arg(logo.section(".", 0, 0))); - auto job = new NetJob(QString("Flame Icon Download %1").arg(logo), APPLICATION->network()); - job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); - - auto fullPath = entry->getFullPath(); - QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] - { - job->deleteLater(); - emit logoLoaded(logo, QIcon(fullPath)); - if(waitingCallbacks.contains(logo)) - { - waitingCallbacks.value(logo)(fullPath); - } - }); - - QObject::connect(job, &NetJob::failed, this, [this, logo, job] - { - job->deleteLater(); - emit logoFailed(logo); - }); - - job->start(); - m_loadingLogos.append(logo); -} - -void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback) -{ - if(m_logoMap.contains(logo)) - { - callback(APPLICATION->metacache()->resolveEntry("FlameMods", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath()); - } - else - { - requestLogo(logo, logoUrl); - } -} - -Qt::ItemFlags ListModel::flags(const QModelIndex &index) const -{ - return QAbstractListModel::flags(index); -} - -bool ListModel::canFetchMore(const QModelIndex& parent) const -{ - return searchState == CanPossiblyFetchMore; -} - -void ListModel::fetchMore(const QModelIndex& parent) -{ - if (parent.isValid()) - return; - if(nextSearchOffset == 0) { - qWarning() << "fetchMore with 0 offset is wrong..."; - return; - } - performPaginatedSearch(); -} -const char* sorts[6]{"Featured","Popularity","LastUpdated","Name","Author","TotalDownloads"}; +const char* sorts[6]{ "Featured", "Popularity", "LastUpdated", "Name", "Author", "TotalDownloads" }; void ListModel::performPaginatedSearch() { - - QString mcVersion = ((MinecraftInstance *)((FlameModPage *)parent())->m_instance)->getPackProfile()->getComponentVersion("net.minecraft"); - bool hasFabric = !((MinecraftInstance *)((FlameModPage *)parent())->m_instance)->getPackProfile()->getComponentVersion("net.fabricmc.fabric-loader").isEmpty(); + QString mcVersion = ((MinecraftInstance*)((FlameModPage*)parent())->m_instance)->getPackProfile()->getComponentVersion("net.minecraft"); + bool hasFabric = !((MinecraftInstance*)((FlameModPage*)parent())->m_instance) + ->getPackProfile() + ->getComponentVersion("net.fabricmc.fabric-loader") + .isEmpty(); auto netJob = new NetJob("Flame::Search", APPLICATION->network()); auto searchUrl = QString( "https://addons-ecs.forgesvc.net/api/v2/addon/search?" @@ -176,44 +32,22 @@ void ListModel::performPaginatedSearch() "searchFilter=%2&" "sort=%3&" "modLoaderType=%4&" - "gameVersion=%5" - ) - .arg(nextSearchOffset) - .arg(currentSearchTerm) - .arg(sorts[currentSort]) - .arg(hasFabric ? 4 : 1) // Enum: https://docs.curseforge.com/?http#tocS_ModLoaderType - .arg(mcVersion); + "gameVersion=%5") + .arg(nextSearchOffset) + .arg(currentSearchTerm) + .arg(sorts[currentSort]) + .arg(hasFabric ? 4 : 1) // Enum: https://docs.curseforge.com/?http#tocS_ModLoaderType + .arg(mcVersion); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); jobPtr = netJob; jobPtr->start(); - QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::searchRequestFinished); - QObject::connect(netJob, &NetJob::failed, this, &ListModel::searchRequestFailed); -} -void ListModel::searchWithTerm(const QString &term, const int sort) -{ - if(currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) { - return; - } - currentSearchTerm = term; - currentSort = sort; - if(jobPtr) { - jobPtr->abort(); - searchState = ResetRequested; - return; - } - else { - beginResetModel(); - modpacks.clear(); - endResetModel(); - searchState = None; - } - nextSearchOffset = 0; - performPaginatedSearch(); + QObject::connect(netJob, &NetJob::succeeded, this, &FlameMod::ListModel::searchRequestFinished); + QObject::connect(netJob, &NetJob::failed, this, &ListModel::searchRequestFailed); } -void ListModel::searchRequestFinished() +void FlameMod::ListModel::searchRequestFinished() { jobPtr.reset(); @@ -253,21 +87,4 @@ void ListModel::searchRequestFinished() endInsertRows(); } -void ListModel::searchRequestFailed(QString reason) -{ - jobPtr.reset(); - - if(searchState == ResetRequested) { - beginResetModel(); - modpacks.clear(); - endResetModel(); - - nextSearchOffset = 0; - performPaginatedSearch(); - } else { - searchState = Finished; - } -} - -} - +} // namespace FlameMod diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.h b/launcher/ui/pages/modplatform/flame/FlameModModel.h index ec19ab2f..a585331d 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModModel.h +++ b/launcher/ui/pages/modplatform/flame/FlameModModel.h @@ -2,78 +2,36 @@ #include -#include -#include -#include #include -#include #include +#include +#include #include #include -#include +#include +#include -#include #include +#include -#include -#include "modplatform/flame/FlameModIndex.h" #include "BaseInstance.h" #include "FlameModPage.h" +#include "modplatform/flame/FlameModIndex.h" namespace FlameMod { - -typedef QMap LogoMap; typedef std::function LogoCallback; -class ListModel : public QAbstractListModel -{ +class ListModel : public ModPlatform::ListModel { Q_OBJECT -public: - ListModel(FlameModPage *parent); + public: + ListModel(FlameModPage* parent); virtual ~ListModel(); - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex &parent) const override; - QVariant data(const QModelIndex &index, int role) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; - bool canFetchMore(const QModelIndex & parent) const override; - void fetchMore(const QModelIndex & parent) override; - - void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback); - void searchWithTerm(const QString &term, const int sort); - -private slots: - void performPaginatedSearch(); - - void logoFailed(QString logo); - void logoLoaded(QString logo, QIcon out); - - void searchRequestFinished(); - void searchRequestFailed(QString reason); - -private: - void requestLogo(QString file, QString url); - -private: - QList modpacks; - QStringList m_failedLogos; - QStringList m_loadingLogos; - LogoMap m_logoMap; - QMap waitingCallbacks; - - QString currentSearchTerm; - int currentSort = 0; - int nextSearchOffset = 0; - enum SearchState { - None, - CanPossiblyFetchMore, - ResetRequested, - Finished - } searchState = None; - NetJob::Ptr jobPtr; - QByteArray response; + private slots: + void performPaginatedSearch() override; + void searchRequestFinished() override; }; -} +} // namespace Modrinth diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp index 48d38b82..ef33f972 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp @@ -1,5 +1,5 @@ #include "FlameModPage.h" -#include "ui_FlameModPage.h" +#include "ui_ModPage.h" #include @@ -12,206 +12,59 @@ #include "minecraft/PackProfile.h" #include "ui/dialogs/ModDownloadDialog.h" -FlameModPage::FlameModPage(ModDownloadDialog *dialog, BaseInstance *instance) - : QWidget(dialog), m_instance(instance), ui(new Ui::FlameModPage), - dialog(dialog) { - ui->setupUi(this); - connect(ui->searchButton, &QPushButton::clicked, this, - &FlameModPage::triggerSearch); - ui->searchEdit->installEventFilter(this); - listModel = new FlameMod::ListModel(this); - ui->packView->setModel(listModel); - - ui->versionSelectionBox->view()->setVerticalScrollBarPolicy( - Qt::ScrollBarAsNeeded); - ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); - - // index is used to set the sorting with the flame api - ui->sortByBox->addItem(tr("Sort by Featured")); - ui->sortByBox->addItem(tr("Sort by Popularity")); - ui->sortByBox->addItem(tr("Sort by last updated")); - ui->sortByBox->addItem(tr("Sort by Name")); - ui->sortByBox->addItem(tr("Sort by Author")); - ui->sortByBox->addItem(tr("Sort by Downloads")); - - connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, - SLOT(triggerSearch())); - connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, - this, &FlameModPage::onSelectionChanged); - connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, - &FlameModPage::onVersionSelectionChanged); - connect(ui->modSelectionButton, &QPushButton::clicked, this, - &FlameModPage::onModSelected); -} - -FlameModPage::~FlameModPage() { delete ui; } - -bool FlameModPage::eventFilter(QObject *watched, QEvent *event) { - if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - if (keyEvent->key() == Qt::Key_Return) { - triggerSearch(); - keyEvent->accept(); - return true; - } - } - return QWidget::eventFilter(watched, event); +FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance) + : ModPage(dialog, instance) +{ + listModel = new FlameMod::ListModel(this); + ui->packView->setModel(listModel); + + // index is used to set the sorting with the flame api + ui->sortByBox->addItem(tr("Sort by Featured")); + ui->sortByBox->addItem(tr("Sort by Popularity")); + ui->sortByBox->addItem(tr("Sort by last updated")); + ui->sortByBox->addItem(tr("Sort by Name")); + ui->sortByBox->addItem(tr("Sort by Author")); + ui->sortByBox->addItem(tr("Sort by Downloads")); + + // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, + // so it's best not to connect them in the parent's contructor... + connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); + connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameModPage::onSelectionChanged); + connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameModPage::onVersionSelectionChanged); + connect(ui->modSelectionButton, &QPushButton::clicked, this, &FlameModPage::onModSelected); } bool FlameModPage::shouldDisplay() const { return true; } -void FlameModPage::openedImpl() { - updateSelectionButton(); - triggerSearch(); -} - -void FlameModPage::triggerSearch() { - listModel->searchWithTerm(ui->searchEdit->text(), - ui->sortByBox->currentIndex()); -} - -void FlameModPage::onSelectionChanged(QModelIndex first, QModelIndex second) { - ui->versionSelectionBox->clear(); - - if (!first.isValid()) { - return; - } - - current = listModel->data(first, Qt::UserRole).value(); - QString text = ""; - QString name = current.name; - - if (current.websiteUrl.isEmpty()) - text = name; - else - text = "" + name + ""; - if (!current.authors.empty()) { - auto authorToStr = [](ModPlatform::ModpackAuthor &author) { - if (author.url.isEmpty()) { - return author.name; - } - return QString("%2").arg(author.url, author.name); - }; - QStringList authorStrs; - for (auto &author : current.authors) { - authorStrs.push_back(authorToStr(author)); +void FlameModPage::onModVersionSucceed(ModPage* instance, QByteArray* response, QString addonId) +{ + if (addonId != current.addonId) { + return; // wrong request } - text += "
" + tr(" by ") + authorStrs.join(", "); - } - text += "

"; - - ui->packDescription->setHtml(text + current.description); - - if (!current.versionsLoaded) { - qDebug() << "Loading flame mod versions"; - - ui->modSelectionButton->setText(tr("Loading versions...")); - ui->modSelectionButton->setEnabled(false); - - auto netJob = - new NetJob(QString("Flame::ModVersions(%1)").arg(current.name), - APPLICATION->network()); - auto response = new QByteArray(); - int addonId = current.addonId.toInt(); - netJob->addNetAction(Net::Download::makeByteArray( - QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files") - .arg(addonId), - response)); - - QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId] { - if(addonId != current.addonId){ - return; //wrong request - } - QJsonParseError parse_error; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from Flame at " - << parse_error.offset - << " reason: " << parse_error.errorString(); + QJsonParseError parse_error; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Flame at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << *response; return; - } - QJsonArray arr = doc.array(); - try { - FlameMod::loadIndexedPackVersions(current, arr, APPLICATION->network(), - m_instance); - } catch (const JSONValidationError &e) { + } + QJsonArray arr = doc.array(); + try { + FlameMod::loadIndexedPackVersions(current, arr, APPLICATION->network(), m_instance); + } catch (const JSONValidationError& e) { qDebug() << *response; qWarning() << "Error while reading Flame mod version: " << e.cause(); - } - auto packProfile = ((MinecraftInstance *)m_instance)->getPackProfile(); - QString mcVersion = packProfile->getComponentVersion("net.minecraft"); - QString loaderString = - (packProfile->getComponentVersion("net.minecraftforge").isEmpty()) - ? "fabric" - : "forge"; - for (int i = 0; i < current.versions.size(); i++) { + } + auto packProfile = ((MinecraftInstance*)m_instance)->getPackProfile(); + QString mcVersion = packProfile->getComponentVersion("net.minecraft"); + QString loaderString = (packProfile->getComponentVersion("net.minecraftforge").isEmpty()) ? "fabric" : "forge"; + for (int i = 0; i < current.versions.size(); i++) { auto version = current.versions[i]; - if (!version.mcVersion.contains(mcVersion)) { - continue; - } + if (!version.mcVersion.contains(mcVersion)) { continue; } ui->versionSelectionBox->addItem(version.version, QVariant(i)); - } - if (ui->versionSelectionBox->count() == 0) { - ui->versionSelectionBox->addItem(tr("No Valid Version found!"), - QVariant(-1)); - } - - ui->modSelectionButton->setText(tr("Cannot select invalid version :(")); - updateSelectionButton(); - }); - QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { - netJob->deleteLater(); - delete response; - }); - netJob->start(); - } else { - for (int i = 0; i < current.versions.s