From c9e851f12f501657629e41339ad604c3cfba82e1 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Sat, 28 Mar 2020 15:55:12 +0100 Subject: GH-2544 enable Forge install button for >= 1.13 --- application/pages/instance/VersionPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'application/pages') diff --git a/application/pages/instance/VersionPage.cpp b/application/pages/instance/VersionPage.cpp index 20298117..60ff8301 100644 --- a/application/pages/instance/VersionPage.cpp +++ b/application/pages/instance/VersionPage.cpp @@ -206,7 +206,7 @@ void VersionPage::updateVersionControls() bool newCraft = controlsEnabled && (minecraftVersion >= Version("1.14")); bool oldCraft = controlsEnabled && (minecraftVersion <= Version("1.12.2")); ui->actionInstall_Fabric->setEnabled(newCraft); - ui->actionInstall_Forge->setEnabled(oldCraft); + ui->actionInstall_Forge->setEnabled(true); ui->actionInstall_LiteLoader->setEnabled(oldCraft); ui->actionReload->setEnabled(true); updateButtons(); -- cgit From 3ff93a42161a5fe9301db1054dcb62c7d79ac77d Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Tue, 31 Mar 2020 03:13:19 +0200 Subject: NOISSUE Bare-bones twitch pack browser --- api/logic/Env.cpp | 1 + application/CMakeLists.txt | 8 +- application/dialogs/NewInstanceDialog.cpp | 3 + application/dialogs/NewInstanceDialog.h | 2 + application/pages/modplatform/twitch/TwitchData.h | 38 ++++ .../pages/modplatform/twitch/TwitchModel.cpp | 253 +++++++++++++++++++++ application/pages/modplatform/twitch/TwitchModel.h | 65 ++++++ .../pages/modplatform/twitch/TwitchPage.cpp | 86 +++++++ application/pages/modplatform/twitch/TwitchPage.h | 77 +++++++ application/pages/modplatform/twitch/TwitchPage.ui | 63 +++++ 10 files changed, 595 insertions(+), 1 deletion(-) create mode 100644 application/pages/modplatform/twitch/TwitchData.h create mode 100644 application/pages/modplatform/twitch/TwitchModel.cpp create mode 100644 application/pages/modplatform/twitch/TwitchModel.h create mode 100644 application/pages/modplatform/twitch/TwitchPage.cpp create mode 100644 application/pages/modplatform/twitch/TwitchPage.h create mode 100644 application/pages/modplatform/twitch/TwitchPage.ui (limited to 'application/pages') diff --git a/api/logic/Env.cpp b/api/logic/Env.cpp index 77546bbc..0d496d4e 100644 --- a/api/logic/Env.cpp +++ b/api/logic/Env.cpp @@ -97,6 +97,7 @@ void Env::initHttpMetaCache() m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath()); m_metacache->addBase("general", QDir("cache").absolutePath()); m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath()); + m_metacache->addBase("TwitchPacks", QDir("cache/TwitchPacks").absolutePath()); m_metacache->addBase("skins", QDir("accounts/skins").absolutePath()); m_metacache->addBase("root", QDir::currentPath()); m_metacache->addBase("translations", QDir("translations").absolutePath()); diff --git a/application/CMakeLists.txt b/application/CMakeLists.txt index 94ba56d0..99ec8b8f 100644 --- a/application/CMakeLists.txt +++ b/application/CMakeLists.txt @@ -133,6 +133,11 @@ SET(MULTIMC_SOURCES pages/modplatform/legacy_ftb/Page.h pages/modplatform/legacy_ftb/ListModel.h pages/modplatform/legacy_ftb/ListModel.cpp + pages/modplatform/twitch/TwitchData.h + pages/modplatform/twitch/TwitchModel.cpp + pages/modplatform/twitch/TwitchModel.h + pages/modplatform/twitch/TwitchPage.cpp + pages/modplatform/twitch/TwitchPage.h pages/modplatform/ImportPage.cpp pages/modplatform/ImportPage.h @@ -251,7 +256,8 @@ SET(MULTIMC_UIS # Platform pages pages/modplatform/VanillaPage.ui pages/modplatform/legacy_ftb/Page.ui - pages/modplatform/ImportPage.ui + pages/modplatform/twitch/TwitchPage.ui + pages/modplatform/twitch/ImportPage.ui # Dialogs dialogs/CopyInstanceDialog.ui diff --git a/application/dialogs/NewInstanceDialog.cpp b/application/dialogs/NewInstanceDialog.cpp index 804340bc..511f991e 100644 --- a/application/dialogs/NewInstanceDialog.cpp +++ b/application/dialogs/NewInstanceDialog.cpp @@ -35,6 +35,7 @@ #include "widgets/PageContainer.h" #include #include +#include #include @@ -119,11 +120,13 @@ void NewInstanceDialog::accept() QList NewInstanceDialog::getPages() { importPage = new ImportPage(this); + twitchPage = new TwitchPage(this); return { new VanillaPage(this), importPage, new LegacyFTB::Page(this), + twitchPage }; } diff --git a/application/dialogs/NewInstanceDialog.h b/application/dialogs/NewInstanceDialog.h index c86ab73f..0b8b2fb8 100644 --- a/application/dialogs/NewInstanceDialog.h +++ b/application/dialogs/NewInstanceDialog.h @@ -29,6 +29,7 @@ class NewInstanceDialog; class PageContainer; class QDialogButtonBox; class ImportPage; +class TwitchPage; class NewInstanceDialog : public QDialog, public BasePageProvider { @@ -67,6 +68,7 @@ private: QString InstIconKey; ImportPage *importPage = nullptr; + TwitchPage *twitchPage = nullptr; std::unique_ptr creationTask; bool importIcon = false; diff --git a/application/pages/modplatform/twitch/TwitchData.h b/application/pages/modplatform/twitch/TwitchData.h new file mode 100644 index 00000000..dd000b84 --- /dev/null +++ b/application/pages/modplatform/twitch/TwitchData.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +namespace Twitch { + +struct ModpackAuthor { + QString name; + QString url; +}; + +struct ModpackFile { + int addonId; + int fileId; + QString version; + QString mcVersion; + QString downloadUrl; +}; + +struct Modpack +{ + bool broken = true; + int addonId = 0; + + QString name; + QString description; + QList authors; + QString mcVersion; + QString logoName; + QString logoUrl; + QString websiteUrl; + + ModpackFile latestFile; +}; +} + +Q_DECLARE_METATYPE(Twitch::Modpack) diff --git a/application/pages/modplatform/twitch/TwitchModel.cpp b/application/pages/modplatform/twitch/TwitchModel.cpp new file mode 100644 index 00000000..210341dc --- /dev/null +++ b/application/pages/modplatform/twitch/TwitchModel.cpp @@ -0,0 +1,253 @@ +#include "TwitchModel.h" +#include "MultiMC.h" + +#include +#include + +#include +#include + +#include +#include + +#include "net/URLConstants.h" + +namespace Twitch { + +ListModel::ListModel(QObject *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); + } + + Modpack 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 = MMC->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); +} + +void ListModel::requestLogo(QString logo, QString url) +{ + if(m_loadingLogos.contains(logo) || m_failedLogos.contains(logo)) + { + return; + } + + MetaEntryPtr entry = ENV.metacache()->resolveEntry("TwitchPacks", QString("logos/%1").arg(logo.section(".", 0, 0))); + NetJob *job = new NetJob(QString("Twitch Icon Download %1").arg(logo)); + job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); + + auto fullPath = entry->getFullPath(); + QObject::connect(job, &NetJob::finished, this, [this, logo, fullPath] + { + emit logoLoaded(logo, QIcon(fullPath)); + if(waitingCallbacks.contains(logo)) + { + waitingCallbacks.value(logo)(fullPath); + } + }); + + QObject::connect(job, &NetJob::failed, this, [this, logo] + { + 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(ENV.metacache()->resolveEntry("TwitchPacks", 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); +} + +void ListModel::searchWithTerm(const QString& term) +{ + if(currentSearchTerm == term) { + return; + } + NetJob *netJob = new NetJob("Twitch::Search"); + auto searchUrl = QString( + "https://addons-ecs.forgesvc.net/api/v2/addon/search?" + "categoryId=0&" + "gameId=432&" + //"gameVersion=1.12.2&" + "index=0&" + "pageSize=25&" + "searchFilter=%1&" + "sectionId=4471&" + "sort=0" + ).arg(term); + 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 Twitch::ListModel::searchRequestFinished() +{ + jobPtr.reset(); + + QJsonParseError parse_error; + QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); + if(parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Twitch at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << response; + return; + } + + QList newList; + auto objs = doc.array(); + for(auto projectIter: objs) { + Modpack pack; + auto project = projectIter.toObject(); + pack.addonId = project.value("id").toInt(0); + if (pack.addonId == 0) { + continue; + } + pack.name = project.value("name").toString(); + pack.websiteUrl = project.value("websiteUrl").toString(); + pack.description = project.value("summary").toString(); + auto attachments = project.value("attachments").toArray(); + for(auto attachmentIter: attachments) { + auto attachment = attachmentIter.toObject(); + bool isDefault = attachment.value("isDefault").toBool(false); + if(!isDefault) { + continue; + } + pack.logoName = attachment.value("title").toString(); + pack.logoUrl = attachment.value("thumbnailUrl").toString(); + } + auto authors = project.value("authors").toArray(); + for(auto authorIter: authors) { + auto author = authorIter.toObject(); + ModpackAuthor packAuthor; + packAuthor.name = author.value("name").toString(); + packAuthor.url = author.value("url").toString(); + pack.authors.append(packAuthor); + } + int defaultFileId = project.value("defaultFileId").toInt(0); + if(defaultFileId == 0) { + continue; + } + bool found = false; + auto files = project.value("latestFiles").toArray(); + for(auto fileIter: files) { + auto file = fileIter.toObject(); + int id = file.value("id").toInt(0); + // NOTE: for now, ignore everything that's not the default... + if(id != defaultFileId) { + continue; + } + pack.latestFile.addonId = pack.addonId; + pack.latestFile.fileId = id; + // FIXME: what to do when there's more than one, or there's no version? + auto versionArray = file.value("gameVersion").toArray(); + if(versionArray.size() != 1) { + continue; + } + pack.latestFile.mcVersion = versionArray[0].toString(); + pack.latestFile.version = file.value("displayName").toString(); + pack.latestFile.downloadUrl = file.value("downloadUrl").toString(); + found = true; + break; + } + if(!found) { + return; + } + pack.broken = false; + newList.append(pack); + } + beginResetModel(); + newList.swap(modpacks); + endResetModel(); +} + +void Twitch::ListModel::searchRequestFailed(QString reason) +{ + jobPtr.reset(); +} + +} diff --git a/application/pages/modplatform/twitch/TwitchModel.h b/application/pages/modplatform/twitch/TwitchModel.h new file mode 100644 index 00000000..1241a079 --- /dev/null +++ b/application/pages/modplatform/twitch/TwitchModel.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "TwitchData.h" + +namespace Twitch { + + +typedef QMap LogoMap; +typedef std::function LogoCallback; + +class ListModel : public QAbstractListModel +{ + Q_OBJECT + +public: + ListModel(QObject *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; + + void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback); + void searchWithTerm(const QString & term); + +private slots: + 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; + NetJobPtr jobPtr; + QByteArray response; +}; + +} diff --git a/application/pages/modplatform/twitch/TwitchPage.cpp b/application/pages/modplatform/twitch/TwitchPage.cpp new file mode 100644 index 00000000..80d83133 --- /dev/null +++ b/application/pages/modplatform/twitch/TwitchPage.cpp @@ -0,0 +1,86 @@ +#include "TwitchPage.h" +#include "ui_TwitchPage.h" + +#include "MultiMC.h" +#include "dialogs/NewInstanceDialog.h" +#include +#include "TwitchModel.h" +#include + +TwitchPage::TwitchPage(NewInstanceDialog* dialog, QWidget *parent) + : QWidget(parent), ui(new Ui::TwitchPage), dialog(dialog) +{ + ui->setupUi(this); + connect(ui->searchButton, &QPushButton::clicked, this, &TwitchPage::triggerSearch); + ui->searchEdit->installEventFilter(this); + model = new Twitch::ListModel(this); + ui->packView->setModel(model); + connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TwitchPage::onSelectionChanged); +} + +TwitchPage::~TwitchPage() +{ + delete ui; +} + +bool TwitchPage::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); +} + +bool TwitchPage::shouldDisplay() const +{ + return true; +} + +void TwitchPage::openedImpl() +{ + suggestCurrent(); +} + +void TwitchPage::triggerSearch() +{ + model->searchWithTerm(ui->searchEdit->text()); +} + +void TwitchPage::onSelectionChanged(QModelIndex first, QModelIndex second) +{ + if(!first.isValid()) + { + if(isOpened) + { + dialog->setSuggestedPack(); + } + return; + } + current = model->data(first, Qt::UserRole).value(); + suggestCurrent(); +} + +void TwitchPage::suggestCurrent() +{ + if(!isOpened) + { + return; + } + if(current.broken) + { + dialog->setSuggestedPack(); + } + + dialog->setSuggestedPack(current.name, new InstanceImportTask(current.latestFile.downloadUrl)); + QString editedLogoName; + editedLogoName = "twitch_" + current.logoName.section(".", 0, 0); + model->getLogo(current.logoName, current.logoUrl, [this, editedLogoName](QString logo) + { + dialog->setSuggestedIconFromFile(logo, editedLogoName); + }); +} diff --git a/application/pages/modplatform/twitch/TwitchPage.h b/application/pages/modplatform/twitch/TwitchPage.h new file mode 100644 index 00000000..04e3a1c6 --- /dev/null +++ b/application/pages/modplatform/twitch/TwitchPage.h @@ -0,0 +1,77 @@ +/* Copyright 2013-2019 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "pages/BasePage.h" +#include +#include "tasks/Task.h" +#include "TwitchData.h" + +namespace Ui +{ +class TwitchPage; +} + +class NewInstanceDialog; + +namespace Twitch { + class ListModel; +} + +class TwitchPage : public QWidget, public BasePage +{ + Q_OBJECT + +public: + explicit TwitchPage(NewInstanceDialog* dialog, QWidget *parent = 0); + virtual ~TwitchPage(); + virtual QString displayName() const override + { + return tr("Twitch"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("twitch"); + } + virtual QString id() const override + { + return "twitch"; + } + virtual QString helpPage() const override + { + return "Twitch-platform"; + } + virtual bool shouldDisplay() const override; + + void openedImpl() override; + + bool eventFilter(QObject * watched, QEvent * event) override; + +private: + void suggestCurrent(); + +private slots: + void triggerSearch(); + void onSelectionChanged(QModelIndex first, QModelIndex second); + +private: + Ui::TwitchPage *ui = nullptr; + NewInstanceDialog* dialog = nullptr; + Twitch::ListModel* model = nullptr; + Twitch::Modpack current; +}; diff --git a/application/pages/modplatform/twitch/TwitchPage.ui b/application/pages/modplatform/twitch/TwitchPage.ui new file mode 100644 index 00000000..7a8203b1 --- /dev/null +++ b/application/pages/modplatform/twitch/TwitchPage.ui @@ -0,0 +1,63 @@ + + + TwitchPage + + + + 0 + 0 + 875 + 745 + + + + + + + + + + Search + + + + + + + Qt::ScrollBarAlwaysOff + + + true + + + + 48 + 48 + + + + false + + + true + + + false + + + true + + + false + + + + + + + searchEdit + searchButton + packView + + + -- cgit From 296ff6de96f8228ae6de8967d6e34436001b3d00 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Wed, 1 Apr 2020 02:08:11 +0200 Subject: NOISSUE Add pagination support to twitch pack search Try searching for 'craft'. Now it gives ALL the results, not just the first page of 25. --- .../pages/modplatform/twitch/TwitchModel.cpp | 87 ++++++++++++++++++---- application/pages/modplatform/twitch/TwitchModel.h | 11 +++ 2 files changed, 85 insertions(+), 13 deletions(-) (limited to 'application/pages') diff --git a/application/pages/modplatform/twitch/TwitchModel.cpp b/application/pages/modplatform/twitch/TwitchModel.cpp index 210341dc..d9358941 100644 --- a/application/pages/modplatform/twitch/TwitchModel.cpp +++ b/application/pages/modplatform/twitch/TwitchModel.cpp @@ -142,23 +142,36 @@ Qt::ItemFlags ListModel::flags(const QModelIndex &index) const return QAbstractListModel::flags(index); } -void ListModel::searchWithTerm(const QString& term) +bool ListModel::canFetchMore(const QModelIndex& parent) const { - if(currentSearchTerm == term) { + 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::performPaginatedSearch() +{ NetJob *netJob = new NetJob("Twitch::Search"); auto searchUrl = QString( "https://addons-ecs.forgesvc.net/api/v2/addon/search?" "categoryId=0&" "gameId=432&" //"gameVersion=1.12.2&" - "index=0&" + "index=%1&" "pageSize=25&" - "searchFilter=%1&" + "searchFilter=%2&" "sectionId=4471&" "sort=0" - ).arg(term); + ).arg(nextSearchOffset).arg(currentSearchTerm); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); jobPtr = netJob; jobPtr->start(); @@ -166,6 +179,27 @@ void ListModel::searchWithTerm(const QString& term) QObject::connect(netJob, &NetJob::failed, this, &ListModel::searchRequestFailed); } +void ListModel::searchWithTerm(const QString& term) +{ + if(currentSearchTerm == term) { + return; + } + currentSearchTerm = term; + if(jobPtr) { + jobPtr->abort(); + searchState = ResetRequested; + return; + } + else { + beginResetModel(); + modpacks.clear(); + endResetModel(); + searchState = None; + } + nextSearchOffset = 0; + performPaginatedSearch(); +} + void Twitch::ListModel::searchRequestFinished() { jobPtr.reset(); @@ -185,20 +219,27 @@ void Twitch::ListModel::searchRequestFinished() auto project = projectIter.toObject(); pack.addonId = project.value("id").toInt(0); if (pack.addonId == 0) { + qWarning() << "Pack without an ID, skipping: " << pack.name; continue; } pack.name = project.value("name").toString(); pack.websiteUrl = project.value("websiteUrl").toString(); pack.description = project.value("summary").toString(); + bool thumbnailFound = false; auto attachments = project.value("attachments").toArray(); for(auto attachmentIter: attachments) { auto attachment = attachmentIter.toObject(); bool isDefault = attachment.value("isDefault").toBool(false); - if(!isDefault) { - continue; + if(isDefault) { + thumbnailFound = true; + pack.logoName = attachment.value("title").toString(); + pack.logoUrl = attachment.value("thumbnailUrl").toString(); + break; } - pack.logoName = attachment.value("title").toString(); - pack.logoUrl = attachment.value("thumbnailUrl").toString(); + } + if(!thumbnailFound) { + qWarning() << "Pack without an icon, skipping: " << pack.name; + continue; } auto authors = project.value("authors").toArray(); for(auto authorIter: authors) { @@ -210,6 +251,7 @@ void Twitch::ListModel::searchRequestFinished() } int defaultFileId = project.value("defaultFileId").toInt(0); if(defaultFileId == 0) { + qWarning() << "Pack without default file, skipping: " << pack.name; continue; } bool found = false; @@ -235,19 +277,38 @@ void Twitch::ListModel::searchRequestFinished() break; } if(!found) { - return; + qWarning() << "Pack with no good file, skipping: " << pack.name; + continue; } pack.broken = false; newList.append(pack); } - beginResetModel(); - newList.swap(modpacks); - endResetModel(); + if(objs.size() < 25) { + searchState = Finished; + } else { + nextSearchOffset += 25; + searchState = CanPossiblyFetchMore; + } + beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1); + modpacks.append(newList); + endInsertRows(); } void Twitch::ListModel::searchRequestFailed(QString reason) { jobPtr.reset(); + + if(searchState == ResetRequested) { + beginResetModel(); + modpacks.clear(); + endResetModel(); + + nextSearchOffset = 0; + performPaginatedSearch(); + } else { + searchState = Finished; + } } } + diff --git a/application/pages/modplatform/twitch/TwitchModel.h b/application/pages/modplatform/twitch/TwitchModel.h index 1241a079..ad355c64 100644 --- a/application/pages/modplatform/twitch/TwitchModel.h +++ b/application/pages/modplatform/twitch/TwitchModel.h @@ -36,11 +36,15 @@ public: 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); private slots: + void performPaginatedSearch(); + void logoFailed(QString logo); void logoLoaded(QString logo, QIcon out); @@ -58,6 +62,13 @@ private: QMap waitingCallbacks; QString currentSearchTerm; + int nextSearchOffset = 0; + enum SearchState { + None, + CanPossiblyFetchMore, + ResetRequested, + Finished + } searchState = None; NetJobPtr jobPtr; QByteArray response; }; -- cgit From 5ca5661c23050d738f1d5f9ced5e7fb71eef3fce Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Wed, 29 Apr 2020 21:17:51 +0200 Subject: NOISSUE expose twitch pack url, description and author list --- .../pages/modplatform/twitch/TwitchPage.cpp | 25 ++++++++++++++++++++++ application/pages/modplatform/twitch/TwitchPage.ui | 25 ++++++++++++++++++++++ 2 files changed, 50 insertions(+) (limited to 'application/pages') diff --git a/application/pages/modplatform/twitch/TwitchPage.cpp b/application/pages/modplatform/twitch/TwitchPage.cpp index 80d83133..1e9f9dbb 100644 --- a/application/pages/modplatform/twitch/TwitchPage.cpp +++ b/application/pages/modplatform/twitch/TwitchPage.cpp @@ -59,9 +59,34 @@ void TwitchPage::onSelectionChanged(QModelIndex first, QModelIndex second) { dialog->setSuggestedPack(); } + ui->frame->clear(); return; } + current = model->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 = [](Twitch::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(", "); + } + + ui->frame->setModText(text); + ui->frame->setModDescription(current.description); suggestCurrent(); } diff --git a/application/pages/modplatform/twitch/TwitchPage.ui b/application/pages/modplatform/twitch/TwitchPage.ui index 7a8203b1..29bdc727 100644 --- a/application/pages/modplatform/twitch/TwitchPage.ui +++ b/application/pages/modplatform/twitch/TwitchPage.ui @@ -52,12 +52,37 @@ + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + MCModInfoFrame + QFrame +
widgets/MCModInfoFrame.h
+ 1 +
+
searchEdit searchButton packView + -- cgit