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.
This commit is contained in:
		
							
								
								
									
										166
									
								
								launcher/ui/pages/modplatform/ModModel.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								launcher/ui/pages/modplatform/ModModel.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,166 @@
 | 
			
		||||
#include "ModModel.h"
 | 
			
		||||
#include "ModPage.h"
 | 
			
		||||
 | 
			
		||||
#include "ui/dialogs/ModDownloadDialog.h"
 | 
			
		||||
 | 
			
		||||
#include <QMessageBox>
 | 
			
		||||
 | 
			
		||||
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("<br>")).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
 | 
			
		||||
							
								
								
									
										60
									
								
								launcher/ui/pages/modplatform/ModModel.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								launcher/ui/pages/modplatform/ModModel.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <QAbstractListModel>
 | 
			
		||||
 | 
			
		||||
#include "modplatform/ModIndex.h"
 | 
			
		||||
#include "net/NetJob.h"
 | 
			
		||||
 | 
			
		||||
class ModPage;
 | 
			
		||||
 | 
			
		||||
namespace ModPlatform {
 | 
			
		||||
 | 
			
		||||
typedef QMap<QString, QIcon> LogoMap;
 | 
			
		||||
typedef std::function<void(QString)> 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<ModPlatform::IndexedPack> modpacks;
 | 
			
		||||
    QStringList m_failedLogos;
 | 
			
		||||
    QStringList m_loadingLogos;
 | 
			
		||||
    LogoMap m_logoMap;
 | 
			
		||||
    QMap<QString, LogoCallback> waitingCallbacks;
 | 
			
		||||
 | 
			
		||||
    QString currentSearchTerm;
 | 
			
		||||
    int currentSort = 0;
 | 
			
		||||
    int nextSearchOffset = 0;
 | 
			
		||||
    enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
 | 
			
		||||
    NetJob::Ptr jobPtr;
 | 
			
		||||
    QByteArray response;
 | 
			
		||||
};
 | 
			
		||||
}  // namespace ModPlatform
 | 
			
		||||
							
								
								
									
										152
									
								
								launcher/ui/pages/modplatform/ModPage.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								launcher/ui/pages/modplatform/ModPage.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,152 @@
 | 
			
		||||
#include "ModPage.h"
 | 
			
		||||
#include "ui_ModPage.h"
 | 
			
		||||
 | 
			
		||||
#include <QKeyEvent>
 | 
			
		||||
 | 
			
		||||
#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<QKeyEvent*>(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<ModPlatform::IndexedPack>();
 | 
			
		||||
    QString text = "";
 | 
			
		||||
    QString name = current.name;
 | 
			
		||||
 | 
			
		||||
    if (current.websiteUrl.isEmpty())
 | 
			
		||||
        text = name;
 | 
			
		||||
    else
 | 
			
		||||
        text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
 | 
			
		||||
    
 | 
			
		||||
    if (!current.authors.empty()) {
 | 
			
		||||
        auto authorToStr = [](ModPlatform::ModpackAuthor& author) {
 | 
			
		||||
            if (author.url.isEmpty()) { return author.name; }
 | 
			
		||||
            return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name);
 | 
			
		||||
        };
 | 
			
		||||
        QStringList authorStrs;
 | 
			
		||||
        for (auto& author : current.authors) {
 | 
			
		||||
            authorStrs.push_back(authorToStr(author));
 | 
			
		||||
        }
 | 
			
		||||
        text += "<br>" + tr(" by ") + authorStrs.join(", ");
 | 
			
		||||
    }
 | 
			
		||||
    text += "<br><br>";
 | 
			
		||||
 | 
			
		||||
    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();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										57
									
								
								launcher/ui/pages/modplatform/ModPage.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								launcher/ui/pages/modplatform/ModPage.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <Application.h>
 | 
			
		||||
#include <QWidget>
 | 
			
		||||
 | 
			
		||||
#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;
 | 
			
		||||
};
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<ui version="4.0">
 | 
			
		||||
 <class>ModrinthPage</class>
 | 
			
		||||
 <widget class="QWidget" name="ModrinthPage">
 | 
			
		||||
 <class>ModPage</class>
 | 
			
		||||
 <widget class="QWidget" name="ModPage">
 | 
			
		||||
  <property name="geometry">
 | 
			
		||||
   <rect>
 | 
			
		||||
    <x>0</x>
 | 
			
		||||
@@ -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 <Json.h>
 | 
			
		||||
 | 
			
		||||
#include <MMCStrings.h>
 | 
			
		||||
#include <Version.h>
 | 
			
		||||
 | 
			
		||||
#include <QtMath>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace FlameMod {
 | 
			
		||||
 | 
			
		||||
ListModel::ListModel(FlameModPage *parent) : QAbstractListModel(parent)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
ListModel::ListModel(FlameModPage* parent) : ModPlatform::ListModel(parent) {}
 | 
			
		||||
 | 
			
		||||
ListModel::~ListModel()
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
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("<br>")).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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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::succeeded, this, &FlameMod::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();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 
 | 
			
		||||
@@ -2,78 +2,36 @@
 | 
			
		||||
 | 
			
		||||
#include <RWStorage.h>
 | 
			
		||||
 | 
			
		||||
#include <QAbstractListModel>
 | 
			
		||||
#include <QSortFilterProxyModel>
 | 
			
		||||
#include <QThreadPool>
 | 
			
		||||
#include <QIcon>
 | 
			
		||||
#include <QStyledItemDelegate>
 | 
			
		||||
#include <QList>
 | 
			
		||||
#include <QMetaType>
 | 
			
		||||
#include <QSortFilterProxyModel>
 | 
			
		||||
#include <QString>
 | 
			
		||||
#include <QStringList>
 | 
			
		||||
#include <QMetaType>
 | 
			
		||||
#include <QStyledItemDelegate>
 | 
			
		||||
#include <QThreadPool>
 | 
			
		||||
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <net/NetJob.h>
 | 
			
		||||
#include <functional>
 | 
			
		||||
 | 
			
		||||
#include <modplatform/flame/FlamePackIndex.h>
 | 
			
		||||
#include "modplatform/flame/FlameModIndex.h"
 | 
			
		||||
#include "BaseInstance.h"
 | 
			
		||||
#include "FlameModPage.h"
 | 
			
		||||
#include "modplatform/flame/FlameModIndex.h"
 | 
			
		||||
 | 
			
		||||
namespace FlameMod {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
typedef QMap<QString, QIcon> LogoMap;
 | 
			
		||||
typedef std::function<void(QString)> 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<ModPlatform::IndexedPack> modpacks;
 | 
			
		||||
    QStringList m_failedLogos;
 | 
			
		||||
    QStringList m_loadingLogos;
 | 
			
		||||
    LogoMap m_logoMap;
 | 
			
		||||
    QMap<QString, LogoCallback> 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
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
#include "FlameModPage.h"
 | 
			
		||||
#include "ui_FlameModPage.h"
 | 
			
		||||
#include "ui_ModPage.h"
 | 
			
		||||
 | 
			
		||||
#include <QKeyEvent>
 | 
			
		||||
 | 
			
		||||
@@ -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);
 | 
			
		||||
FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance) 
 | 
			
		||||
    : ModPage(dialog, instance)
 | 
			
		||||
{
 | 
			
		||||
    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"));
 | 
			
		||||
 | 
			
		||||
  // 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<QKeyEvent *>(event);
 | 
			
		||||
    if (keyEvent->key() == Qt::Key_Return) {
 | 
			
		||||
      triggerSearch();
 | 
			
		||||
      keyEvent->accept();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return QWidget::eventFilter(watched, event);
 | 
			
		||||
    // 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<ModPlatform::IndexedPack>();
 | 
			
		||||
  QString text = "";
 | 
			
		||||
  QString name = current.name;
 | 
			
		||||
 | 
			
		||||
  if (current.websiteUrl.isEmpty())
 | 
			
		||||
    text = name;
 | 
			
		||||
  else
 | 
			
		||||
    text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
 | 
			
		||||
  if (!current.authors.empty()) {
 | 
			
		||||
    auto authorToStr = [](ModPlatform::ModpackAuthor &author) {
 | 
			
		||||
      if (author.url.isEmpty()) {
 | 
			
		||||
        return author.name;
 | 
			
		||||
      }
 | 
			
		||||
      return QString("<a href=\"%1\">%2</a>").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 += "<br>" + tr(" by ") + authorStrs.join(", ");
 | 
			
		||||
  }
 | 
			
		||||
  text += "<br><br>";
 | 
			
		||||
 | 
			
		||||
  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 version = current.versions[i];
 | 
			
		||||
        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 {
 | 
			
		||||
    }
 | 
			
		||||
    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++) {
 | 
			
		||||
      ui->versionSelectionBox->addItem(current.versions[i].version,
 | 
			
		||||
                                       QVariant(i));
 | 
			
		||||
    }
 | 
			
		||||
    if (ui->versionSelectionBox->count() == 0) {
 | 
			
		||||
      ui->versionSelectionBox->addItem(tr("No Valid Version found!"),
 | 
			
		||||
                                       QVariant(-1));
 | 
			
		||||
        auto version = current.versions[i];
 | 
			
		||||
        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();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FlameModPage::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 FlameModPage::onVersionSelectionChanged(QString data) {
 | 
			
		||||
  if (data.isNull() || data.isEmpty()) {
 | 
			
		||||
    selectedVersion = -1;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  selectedVersion = ui->versionSelectionBox->currentData().toInt();
 | 
			
		||||
  updateSelectionButton();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FlameModPage::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();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,68 +1,24 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <QWidget>
 | 
			
		||||
#include "ui/pages/modplatform/ModPage.h"
 | 
			
		||||
 | 
			
		||||
#include "ui/pages/BasePage.h"
 | 
			
		||||
#include <Application.h>
 | 
			
		||||
#include "tasks/Task.h"
 | 
			
		||||
#include "modplatform/flame/FlameModIndex.h"
 | 
			
		||||
 | 
			
		||||
namespace Ui
 | 
			
		||||
{
 | 
			
		||||
class FlameModPage;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ModDownloadDialog;
 | 
			
		||||
 | 
			
		||||
namespace FlameMod {
 | 
			
		||||
    class ListModel;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class FlameModPage : public QWidget, public BasePage
 | 
			
		||||
{
 | 
			
		||||
class FlameModPage : public ModPage {
 | 
			
		||||
    Q_OBJECT
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    explicit FlameModPage(ModDownloadDialog *dialog, BaseInstance *instance);
 | 
			
		||||
    virtual ~FlameModPage();
 | 
			
		||||
    virtual QString displayName() const override
 | 
			
		||||
    {
 | 
			
		||||
        return tr("CurseForge");
 | 
			
		||||
    }
 | 
			
		||||
    virtual QIcon icon() const override
 | 
			
		||||
    {
 | 
			
		||||
        return APPLICATION->getThemedIcon("flame");
 | 
			
		||||
    }
 | 
			
		||||
    virtual QString id() const override
 | 
			
		||||
    {
 | 
			
		||||
        return "curseforge";
 | 
			
		||||
    }
 | 
			
		||||
    virtual QString helpPage() const override
 | 
			
		||||
    {
 | 
			
		||||
        return "Flame-platform";
 | 
			
		||||
    }
 | 
			
		||||
    virtual bool shouldDisplay() const override;
 | 
			
		||||
   public:
 | 
			
		||||
    explicit FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance);
 | 
			
		||||
    virtual ~FlameModPage() = default;
 | 
			
		||||
 | 
			
		||||
    void openedImpl() override;
 | 
			
		||||
    inline QString displayName() const override { return tr("CurseForge"); }
 | 
			
		||||
    inline QIcon icon() const override { return APPLICATION->getThemedIcon("flame"); }
 | 
			
		||||
    inline QString id() const override { return "curseforge"; }
 | 
			
		||||
    inline QString helpPage() const override { return "Flame-platform"; }
 | 
			
		||||
 | 
			
		||||
    bool eventFilter(QObject * watched, QEvent * event) override;
 | 
			
		||||
    inline QString debugName() const override { return tr("Flame"); }
 | 
			
		||||
    inline QString metaEntryBase() const override { return "FlameMods"; };
 | 
			
		||||
 | 
			
		||||
    BaseInstance *m_instance;
 | 
			
		||||
    bool shouldDisplay() const override;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void updateSelectionButton();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
    void triggerSearch();
 | 
			
		||||
    void onSelectionChanged(QModelIndex first, QModelIndex second);
 | 
			
		||||
    void onVersionSelectionChanged(QString data);
 | 
			
		||||
    void onModSelected();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    Ui::FlameModPage *ui = nullptr;
 | 
			
		||||
    ModDownloadDialog* dialog = nullptr;
 | 
			
		||||
    FlameMod::ListModel* listModel = nullptr;
 | 
			
		||||
    ModPlatform::IndexedPack current;
 | 
			
		||||
 | 
			
		||||
    int selectedVersion = -1;
 | 
			
		||||
   private:
 | 
			
		||||
    void onModVersionSucceed(ModPage*, QByteArray*, QString) override;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,97 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<ui version="4.0">
 | 
			
		||||
 <class>FlameModPage</class>
 | 
			
		||||
 <widget class="QWidget" name="FlameModPage">
 | 
			
		||||
  <property name="geometry">
 | 
			
		||||
   <rect>
 | 
			
		||||
    <x>0</x>
 | 
			
		||||
    <y>0</y>
 | 
			
		||||
    <width>837</width>
 | 
			
		||||
    <height>685</height>
 | 
			
		||||
   </rect>
 | 
			
		||||
  </property>
 | 
			
		||||
  <layout class="QGridLayout" name="gridLayout">
 | 
			
		||||
   <item row="2" column="0" colspan="2">
 | 
			
		||||
    <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0,0,0" columnminimumwidth="0,0,0">
 | 
			
		||||
     <item row="1" column="2">
 | 
			
		||||
      <widget class="QComboBox" name="versionSelectionBox"/>
 | 
			
		||||
     </item>
 | 
			
		||||
     <item row="1" column="0">
 | 
			
		||||
      <widget class="QComboBox" name="sortByBox"/>
 | 
			
		||||
     </item>
 | 
			
		||||
     <item row="1" column="1">
 | 
			
		||||
      <widget class="QLabel" name="label">
 | 
			
		||||
       <property name="text">
 | 
			
		||||
        <string>Version selected:</string>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="alignment">
 | 
			
		||||
        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
 | 
			
		||||
       </property>
 | 
			
		||||
      </widget>
 | 
			
		||||
     </item>
 | 
			
		||||
     <item row="2" column="2">
 | 
			
		||||
      <widget class="QPushButton" name="modSelectionButton">
 | 
			
		||||
       <property name="text">
 | 
			
		||||
        <string>Select mod for download</string>
 | 
			
		||||
       </property>
 | 
			
		||||
      </widget>
 | 
			
		||||
     </item>
 | 
			
		||||
    </layout>
 | 
			
		||||
   </item>
 | 
			
		||||
   <item row="0" column="0">
 | 
			
		||||
    <widget class="QLineEdit" name="searchEdit">
 | 
			
		||||
     <property name="placeholderText">
 | 
			
		||||
      <string>Search and filter ...</string>
 | 
			
		||||
     </property>
 | 
			
		||||
    </widget>
 | 
			
		||||
   </item>
 | 
			
		||||
   <item row="1" column="0" colspan="2">
 | 
			
		||||
    <layout class="QGridLayout" name="gridLayout_3">
 | 
			
		||||
     <item row="1" column="0">
 | 
			
		||||
      <widget class="QListView" name="packView">
 | 
			
		||||
       <property name="horizontalScrollBarPolicy">
 | 
			
		||||
        <enum>Qt::ScrollBarAlwaysOff</enum>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="alternatingRowColors">
 | 
			
		||||
        <bool>true</bool>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="iconSize">
 | 
			
		||||
        <size>
 | 
			
		||||
         <width>48</width>
 | 
			
		||||
         <height>48</height>
 | 
			
		||||
        </size>
 | 
			
		||||
       </property>
 | 
			
		||||
      </widget>
 | 
			
		||||
     </item>
 | 
			
		||||
     <item row="1" column="1">
 | 
			
		||||
      <widget class="QTextBrowser" name="packDescription">
 | 
			
		||||
       <property name="openExternalLinks">
 | 
			
		||||
        <bool>true</bool>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="openLinks">
 | 
			
		||||
        <bool>true</bool>
 | 
			
		||||
       </property>
 | 
			
		||||
      </widget>
 | 
			
		||||
     </item>
 | 
			
		||||
    </layout>
 | 
			
		||||
   </item>
 | 
			
		||||
   <item row="0" column="1">
 | 
			
		||||
    <widget class="QPushButton" name="searchButton">
 | 
			
		||||
     <property name="text">
 | 
			
		||||
      <string>Search</string>
 | 
			
		||||
     </property>
 | 
			
		||||
    </widget>
 | 
			
		||||
   </item>
 | 
			
		||||
  </layout>
 | 
			
		||||
 </widget>
 | 
			
		||||
 <tabstops>
 | 
			
		||||
  <tabstop>searchEdit</tabstop>
 | 
			
		||||
  <tabstop>searchButton</tabstop>
 | 
			
		||||
  <tabstop>packView</tabstop>
 | 
			
		||||
  <tabstop>packDescription</tabstop>
 | 
			
		||||
  <tabstop>sortByBox</tabstop>
 | 
			
		||||
  <tabstop>versionSelectionBox</tabstop>
 | 
			
		||||
 </tabstops>
 | 
			
		||||
 <resources/>
 | 
			
		||||
 <connections/>
 | 
			
		||||
</ui>
 | 
			
		||||
@@ -1,171 +1,25 @@
 | 
			
		||||
#include "ModrinthModel.h"
 | 
			
		||||
#include "Application.h"
 | 
			
		||||
#include "ModrinthPage.h"
 | 
			
		||||
#include "minecraft/MinecraftInstance.h"
 | 
			
		||||
#include "minecraft/PackProfile.h"
 | 
			
		||||
#include "ModrinthPage.h"
 | 
			
		||||
#include "ui/dialogs/ModDownloadDialog.h"
 | 
			
		||||
 | 
			
		||||
#include <Json.h>
 | 
			
		||||
 | 
			
		||||
#include <MMCStrings.h>
 | 
			
		||||
#include <Version.h>
 | 
			
		||||
 | 
			
		||||
#include <QtMath>
 | 
			
		||||
#include <QMessageBox>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace Modrinth {
 | 
			
		||||
 | 
			
		||||
ListModel::ListModel(ModrinthPage *parent) : QAbstractListModel(parent)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
ListModel::ListModel(ModrinthPage* parent) : ModPlatform::ListModel(parent) {}
 | 
			
		||||
 | 
			
		||||
ListModel::~ListModel()
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
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("<br>")).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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ListModel::requestLogo(QString logo, QString url)
 | 
			
		||||
{
 | 
			
		||||
    if(m_loadingLogos.contains(logo) || m_failedLogos.contains(logo))
 | 
			
		||||
    {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ModrinthPacks", QString("logos/%1").arg(logo.section(".", 0, 0)));
 | 
			
		||||
    auto job = new NetJob(QString("Modrinth 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("ModrinthPacks", 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[5]{"relevance","downloads","follows","updated","newest"};
 | 
			
		||||
const char* sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" };
 | 
			
		||||
 | 
			
		||||
void ListModel::performPaginatedSearch()
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    QString mcVersion =  ((MinecraftInstance *)((ModrinthPage *)parent())->m_instance)->getPackProfile()->getComponentVersion("net.minecraft");
 | 
			
		||||
    bool hasFabric = !((MinecraftInstance *)((ModrinthPage *)parent())->m_instance)->getPackProfile()->getComponentVersion("net.fabricmc.fabric-loader").isEmpty();
 | 
			
		||||
    QString mcVersion = ((MinecraftInstance*)((ModrinthPage*)parent())->m_instance)->getPackProfile()->getComponentVersion("net.minecraft");
 | 
			
		||||
    bool hasFabric = !((MinecraftInstance*)((ModrinthPage*)parent())->m_instance)
 | 
			
		||||
                          ->getPackProfile()
 | 
			
		||||
                          ->getComponentVersion("net.fabricmc.fabric-loader")
 | 
			
		||||
                          .isEmpty();
 | 
			
		||||
    auto netJob = new NetJob("Modrinth::Search", APPLICATION->network());
 | 
			
		||||
    auto searchUrl = QString(
 | 
			
		||||
        "https://api.modrinth.com/v2/search?"
 | 
			
		||||
@@ -173,41 +27,19 @@ void ListModel::performPaginatedSearch()
 | 
			
		||||
        "limit=25&"
 | 
			
		||||
        "query=%2&"
 | 
			
		||||
        "index=%3&"
 | 
			
		||||
        "facets=[[\"categories:%4\"],[\"versions:%5\"],[\"project_type:mod\"]]"
 | 
			
		||||
    )
 | 
			
		||||
        .arg(nextSearchOffset)
 | 
			
		||||
        .arg(currentSearchTerm)
 | 
			
		||||
        .arg(sorts[currentSort])
 | 
			
		||||
        .arg(hasFabric ? "fabric" : "forge")
 | 
			
		||||
        .arg(mcVersion);
 | 
			
		||||
        "facets=[[\"categories:%4\"],[\"versions:%5\"],[\"project_type:mod\"]]")
 | 
			
		||||
            .arg(nextSearchOffset)
 | 
			
		||||
            .arg(currentSearchTerm)
 | 
			
		||||
            .arg(sorts[currentSort])
 | 
			
		||||
            .arg(hasFabric ? "fabric" : "forge")
 | 
			
		||||
            .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, &Modrinth::ListModel::searchRequestFinished);
 | 
			
		||||
    QObject::connect(netJob, &NetJob::failed, this, &ListModel::searchRequestFailed);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Modrinth::ListModel::searchRequestFinished()
 | 
			
		||||
@@ -216,30 +48,28 @@ void Modrinth::ListModel::searchRequestFinished()
 | 
			
		||||
 | 
			
		||||
    QJsonParseError parse_error;
 | 
			
		||||
    QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
 | 
			
		||||
    if(parse_error.error != QJsonParseError::NoError) {
 | 
			
		||||
        qWarning() << "Error while parsing JSON response from Modrinth at " << parse_error.offset << " reason: " << parse_error.errorString();
 | 
			
		||||
    if (parse_error.error != QJsonParseError::NoError) {
 | 
			
		||||
        qWarning() << "Error while parsing JSON response from Modrinth at " << parse_error.offset
 | 
			
		||||
                   << " reason: " << parse_error.errorString();
 | 
			
		||||
        qWarning() << response;
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    QList<ModPlatform::IndexedPack> newList;
 | 
			
		||||
    auto packs = doc.object().value("hits").toArray();
 | 
			
		||||
    for(auto packRaw : packs) {
 | 
			
		||||
    for (auto packRaw : packs) {
 | 
			
		||||
        auto packObj = packRaw.toObject();
 | 
			
		||||
 | 
			
		||||
        ModPlatform::IndexedPack pack;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
        try {
 | 
			
		||||
            Modrinth::loadIndexedPack(pack, packObj);
 | 
			
		||||
            newList.append(pack);
 | 
			
		||||
        }
 | 
			
		||||
        catch(const JSONValidationError &e)
 | 
			
		||||
        {
 | 
			
		||||
        } catch (const JSONValidationError& e) {
 | 
			
		||||
            qWarning() << "Error while loading mod from Modrinth: " << e.cause();
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if(packs.size() < 25) {
 | 
			
		||||
    if (packs.size() < 25) {
 | 
			
		||||
        searchState = Finished;
 | 
			
		||||
    } else {
 | 
			
		||||
        nextSearchOffset += 25;
 | 
			
		||||
@@ -250,27 +80,4 @@ void Modrinth::ListModel::searchRequestFinished()
 | 
			
		||||
    endInsertRows();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Modrinth::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"), tr("Modrinth API version too old!\nPlease update PolyMC!"));
 | 
			
		||||
        //self-destruct
 | 
			
		||||
        ((ModDownloadDialog *)((ModrinthPage *)parent())->parentWidget())->reject();
 | 
			
		||||
    }
 | 
			
		||||
    jobPtr.reset();
 | 
			
		||||
 | 
			
		||||
    if(searchState == ResetRequested) {
 | 
			
		||||
        beginResetModel();
 | 
			
		||||
        modpacks.clear();
 | 
			
		||||
        endResetModel();
 | 
			
		||||
 | 
			
		||||
        nextSearchOffset = 0;
 | 
			
		||||
        performPaginatedSearch();
 | 
			
		||||
    } else {
 | 
			
		||||
        searchState = Finished;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace Modrinth
 | 
			
		||||
 
 | 
			
		||||
@@ -2,78 +2,36 @@
 | 
			
		||||
 | 
			
		||||
#include <RWStorage.h>
 | 
			
		||||
 | 
			
		||||
#include <QAbstractListModel>
 | 
			
		||||
#include <QSortFilterProxyModel>
 | 
			
		||||
#include <QThreadPool>
 | 
			
		||||
#include <QIcon>
 | 
			
		||||
#include <QStyledItemDelegate>
 | 
			
		||||
#include <QList>
 | 
			
		||||
#include <QMetaType>
 | 
			
		||||
#include <QSortFilterProxyModel>
 | 
			
		||||
#include <QString>
 | 
			
		||||
#include <QStringList>
 | 
			
		||||
#include <QMetaType>
 | 
			
		||||
#include <QStyledItemDelegate>
 | 
			
		||||
#include <QThreadPool>
 | 
			
		||||
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <net/NetJob.h>
 | 
			
		||||
#include <functional>
 | 
			
		||||
 | 
			
		||||
#include <modplatform/flame/FlamePackIndex.h>
 | 
			
		||||
#include "modplatform/modrinth/ModrinthPackIndex.h"
 | 
			
		||||
#include "BaseInstance.h"
 | 
			
		||||
#include "ModrinthPage.h"
 | 
			
		||||
#include "modplatform/modrinth/ModrinthPackIndex.h"
 | 
			
		||||
 | 
			
		||||
namespace Modrinth {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
typedef QMap<QString, QIcon> LogoMap;
 | 
			
		||||
typedef std::function<void(QString)> LogoCallback;
 | 
			
		||||
 | 
			
		||||
class ListModel : public QAbstractListModel
 | 
			
		||||
{
 | 
			
		||||
class ListModel : public ModPlatform::ListModel {
 | 
			
		||||
    Q_OBJECT
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    ListModel(ModrinthPage *parent);
 | 
			
		||||
   public:
 | 
			
		||||
    ListModel(ModrinthPage* 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<ModPlatform::IndexedPack> modpacks;
 | 
			
		||||
    QStringList m_failedLogos;
 | 
			
		||||
    QStringList m_loadingLogos;
 | 
			
		||||
    LogoMap m_logoMap;
 | 
			
		||||
    QMap<QString, LogoCallback> 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
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
#include "ModrinthPage.h"
 | 
			
		||||
#include "ui_ModrinthPage.h"
 | 
			
		||||
#include "ui_ModPage.h"
 | 
			
		||||
 | 
			
		||||
#include <QKeyEvent>
 | 
			
		||||
 | 
			
		||||
@@ -12,194 +12,57 @@
 | 
			
		||||
#include "minecraft/PackProfile.h"
 | 
			
		||||
#include "ui/dialogs/ModDownloadDialog.h"
 | 
			
		||||
 | 
			
		||||
ModrinthPage::ModrinthPage(ModDownloadDialog *dialog, BaseInstance *instance)
 | 
			
		||||
    : QWidget(dialog), m_instance(instance), ui(new Ui::ModrinthPage),
 | 
			
		||||
      dialog(dialog) {
 | 
			
		||||
  ui->setupUi(this);
 | 
			
		||||
  connect(ui->searchButton, &QPushButton::clicked, this,
 | 
			
		||||
          &ModrinthPage::triggerSearch);
 | 
			
		||||
  ui->searchEdit->installEventFilter(this);
 | 
			
		||||
  listModel = new Modrinth::ListModel(this);
 | 
			
		||||
  ui->packView->setModel(listModel);
 | 
			
		||||
ModrinthPage::ModrinthPage(ModDownloadDialog* dialog, BaseInstance* instance)
 | 
			
		||||
    : ModPage(dialog, instance)
 | 
			
		||||
{
 | 
			
		||||
    listModel = new Modrinth::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 modrinth api
 | 
			
		||||
    ui->sortByBox->addItem(tr("Sort by Relevence"));
 | 
			
		||||
    ui->sortByBox->addItem(tr("Sort by Downloads"));
 | 
			
		||||
    ui->sortByBox->addItem(tr("Sort by Follows"));
 | 
			
		||||
    ui->sortByBox->addItem(tr("Sort by last updated"));
 | 
			
		||||
    ui->sortByBox->addItem(tr("Sort by newest"));
 | 
			
		||||
 | 
			
		||||
  // index is used to set the sorting with the modrinth api
 | 
			
		||||
  ui->sortByBox->addItem(tr("Sort by Relevence"));
 | 
			
		||||
  ui->sortByBox->addItem(tr("Sort by Downloads"));
 | 
			
		||||
  ui->sortByBox->addItem(tr("Sort by Follows"));
 | 
			
		||||
  ui->sortByBox->addItem(tr("Sort by last updated"));
 | 
			
		||||
  ui->sortByBox->addItem(tr("Sort by newest"));
 | 
			
		||||
 | 
			
		||||
  connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this,
 | 
			
		||||
          SLOT(triggerSearch()));
 | 
			
		||||
  connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged,
 | 
			
		||||
          this, &ModrinthPage::onSelectionChanged);
 | 
			
		||||
  connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this,
 | 
			
		||||
          &ModrinthPage::onVersionSelectionChanged);
 | 
			
		||||
  connect(ui->modSelectionButton, &QPushButton::clicked, this,
 | 
			
		||||
          &ModrinthPage::onModSelected);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ModrinthPage::~ModrinthPage() { delete ui; }
 | 
			
		||||
 | 
			
		||||
bool ModrinthPage::eventFilter(QObject *watched, QEvent *event) {
 | 
			
		||||
  if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
 | 
			
		||||
    QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
 | 
			
		||||
    if (keyEvent->key() == Qt::Key_Return) {
 | 
			
		||||
      triggerSearch();
 | 
			
		||||
      keyEvent->accept();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return QWidget::eventFilter(watched, event);
 | 
			
		||||
    // 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, &ModrinthPage::onSelectionChanged);
 | 
			
		||||
    connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthPage::onVersionSelectionChanged);
 | 
			
		||||
    connect(ui->modSelectionButton, &QPushButton::clicked, this, &ModrinthPage::onModSelected);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ModrinthPage::shouldDisplay() const { return true; }
 | 
			
		||||
 | 
			
		||||
void ModrinthPage::openedImpl() {
 | 
			
		||||
  updateSelectionButton();
 | 
			
		||||
  triggerSearch();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ModrinthPage::triggerSearch() {
 | 
			
		||||
  listModel->searchWithTerm(ui->searchEdit->text(),
 | 
			
		||||
                            ui->sortByBox->currentIndex());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) {
 | 
			
		||||
  ui->versionSelectionBox->clear();
 | 
			
		||||
 | 
			
		||||
  if (!first.isValid()) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  current = listModel->data(first, Qt::UserRole).value<ModPlatform::IndexedPack>();
 | 
			
		||||
  QString text = "";
 | 
			
		||||
  QString name = current.name;
 | 
			
		||||
 | 
			
		||||
  if (current.websiteUrl.isEmpty())
 | 
			
		||||
    text = name;
 | 
			
		||||
  else
 | 
			
		||||
    text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
 | 
			
		||||
  text += "<br>" + tr(" by ") + "<a href=\"" + current.authors[0].url + "\">" +
 | 
			
		||||
          current.authors[0].name + "</a><br><br>";
 | 
			
		||||
  ui->packDescription->setHtml(text + current.description);
 | 
			
		||||
 | 
			
		||||
  if (!current.versionsLoaded) {
 | 
			
		||||
    qDebug() << "Loading Modrinth mod versions";
 | 
			
		||||
 | 
			
		||||
    ui->modSelectionButton->setText(tr("Loading versions..."));
 | 
			
		||||
    ui->modSelectionButton->setEnabled(false);
 | 
			
		||||
 | 
			
		||||
    auto netJob =
 | 
			
		||||
        new NetJob(QString("Modrinth::ModVersions(%1)").arg(current.name),
 | 
			
		||||
                   APPLICATION->network());
 | 
			
		||||
    auto response = new QByteArray();
 | 
			
		||||
    QString addonId = current.addonId.toString();
 | 
			
		||||
    netJob->addNetAction(Net::Download::makeByteArray(
 | 
			
		||||
        QString("https://api.modrinth.com/v2/project/%1/version").arg(addonId),
 | 
			
		||||
        response));
 | 
			
		||||
 | 
			
		||||
    QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId] {
 | 
			
		||||
        if(addonId != current.addonId){
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
      QJsonParseError parse_error;
 | 
			
		||||
      QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
 | 
			
		||||
      if (parse_error.error != QJsonParseError::NoError) {
 | 
			
		||||
        qWarning() << "Error while parsing JSON response from Modrinth at "
 | 
			
		||||
                   << parse_error.offset
 | 
			
		||||
void ModrinthPage::onModVersionSucceed(ModPage* instance, QByteArray* response, QString addonId)
 | 
			
		||||
{
 | 
			
		||||
    if (addonId != current.addonId) { return; }
 | 
			
		||||
    QJsonParseError parse_error;
 | 
			
		||||
    QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
 | 
			
		||||
    if (parse_error.error != QJsonParseError::NoError) {
 | 
			
		||||
        qWarning() << "Error while parsing JSON response from Modrinth at " << parse_error.offset
 | 
			
		||||
                   << " reason: " << parse_error.errorString();
 | 
			
		||||
        qWarning() << *response;
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      QJsonArray arr = doc.array();
 | 
			
		||||
      try {
 | 
			
		||||
        Modrinth::loadIndexedPackVersions(current, arr, APPLICATION->network(),
 | 
			
		||||
                                          m_instance);
 | 
			
		||||
      } catch (const JSONValidationError &e) {
 | 
			
		||||
    }
 | 
			
		||||
    QJsonArray arr = doc.array();
 | 
			
		||||
    try {
 | 
			
		||||
        Modrinth::loadIndexedPackVersions(current, arr, APPLICATION->network(), m_instance);
 | 
			
		||||
    } catch (const JSONValidationError& e) {
 | 
			
		||||
        qDebug() << *response;
 | 
			
		||||
        qWarning() << "Error while reading Modrinth 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 version = current.versions[i];
 | 
			
		||||
        if (!version.mcVersion.contains(mcVersion) ||
 | 
			
		||||
            !version.loaders.contains(loaderString)) {
 | 
			
		||||
          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 {
 | 
			
		||||
    }
 | 
			
		||||
    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++) {
 | 
			
		||||
      ui->versionSelectionBox->addItem(current.versions[i].version,
 | 
			
		||||
                                       QVariant(i));
 | 
			
		||||
    }
 | 
			
		||||
    if (ui->versionSelectionBox->count() == 0) {
 | 
			
		||||
      ui->versionSelectionBox->addItem(tr("No Valid Version found !"),
 | 
			
		||||
                                       QVariant(-1));
 | 
			
		||||
        auto version = current.versions[i];
 | 
			
		||||
        if (!version.mcVersion.contains(mcVersion) || !version.loaders.contains(loaderString)) { 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();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ModrinthPage::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 ModrinthPage::onVersionSelectionChanged(QString data) {
 | 
			
		||||
  if (data.isNull() || data.isEmpty()) {
 | 
			
		||||
    selectedVersion = -1;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  selectedVersion = ui->versionSelectionBox->currentData().toInt();
 | 
			
		||||
  updateSelectionButton();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ModrinthPage::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();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,68 +1,24 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <QWidget>
 | 
			
		||||
#include "ui/pages/modplatform/ModPage.h"
 | 
			
		||||
 | 
			
		||||
#include "ui/pages/BasePage.h"
 | 
			
		||||
#include <Application.h>
 | 
			
		||||
#include "tasks/Task.h"
 | 
			
		||||
#include "modplatform/modrinth/ModrinthPackIndex.h"
 | 
			
		||||
 | 
			
		||||
namespace Ui
 | 
			
		||||
{
 | 
			
		||||
class ModrinthPage;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ModDownloadDialog;
 | 
			
		||||
 | 
			
		||||
namespace Modrinth {
 | 
			
		||||
    class ListModel;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ModrinthPage : public QWidget, public BasePage
 | 
			
		||||
{
 | 
			
		||||
class ModrinthPage : public ModPage {
 | 
			
		||||
    Q_OBJECT
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    explicit ModrinthPage(ModDownloadDialog *dialog, BaseInstance *instance);
 | 
			
		||||
    virtual ~ModrinthPage();
 | 
			
		||||
    virtual QString displayName() const override
 | 
			
		||||
    {
 | 
			
		||||
        return tr("Modrinth");
 | 
			
		||||
    }
 | 
			
		||||
    virtual QIcon icon() const override
 | 
			
		||||
    {
 | 
			
		||||
        return APPLICATION->getThemedIcon("modrinth");
 | 
			
		||||
    }
 | 
			
		||||
    virtual QString id() const override
 | 
			
		||||
    {
 | 
			
		||||
        return "modrinth";
 | 
			
		||||
    }
 | 
			
		||||
    virtual QString helpPage() const override
 | 
			
		||||
    {
 | 
			
		||||
        return "Modrinth-platform";
 | 
			
		||||
    }
 | 
			
		||||
    virtual bool shouldDisplay() const override;
 | 
			
		||||
   public:
 | 
			
		||||
    explicit ModrinthPage(ModDownloadDialog* dialog, BaseInstance* instance);
 | 
			
		||||
    virtual ~ModrinthPage() = default;
 | 
			
		||||
 | 
			
		||||
    void openedImpl() override;
 | 
			
		||||
    inline QString displayName() const override { return tr("Modrinth"); }
 | 
			
		||||
    inline QIcon icon() const override { return APPLICATION->getThemedIcon("modrinth"); }
 | 
			
		||||
    inline QString id() const override { return "modrinth"; }
 | 
			
		||||
    inline QString helpPage() const override { return "Modrinth-platform"; }
 | 
			
		||||
 | 
			
		||||
    bool eventFilter(QObject * watched, QEvent * event) override;
 | 
			
		||||
    inline QString debugName() const override { return tr("Modrinth"); }
 | 
			
		||||
    inline QString metaEntryBase() const override { return "ModrinthPacks"; };
 | 
			
		||||
 | 
			
		||||
    BaseInstance *m_instance;
 | 
			
		||||
    bool shouldDisplay() const override;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void updateSelectionButton();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
    void triggerSearch();
 | 
			
		||||
    void onSelectionChanged(QModelIndex first, QModelIndex second);
 | 
			
		||||
    void onVersionSelectionChanged(QString data);
 | 
			
		||||
    void onModSelected();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    Ui::ModrinthPage *ui = nullptr;
 | 
			
		||||
    ModDownloadDialog* dialog = nullptr;
 | 
			
		||||
    Modrinth::ListModel* listModel = nullptr;
 | 
			
		||||
    ModPlatform::IndexedPack current;
 | 
			
		||||
 | 
			
		||||
    int selectedVersion = -1;
 | 
			
		||||
   private:
 | 
			
		||||
    void onModVersionSucceed(ModPage*, QByteArray*, QString) override;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user