aboutsummaryrefslogtreecommitdiff
path: root/launcher/ui
diff options
context:
space:
mode:
authorflow <flowlnlnln@gmail.com>2022-05-17 18:25:08 -0300
committerflow <flowlnlnln@gmail.com>2022-05-17 18:25:08 -0300
commitff9f3cb31ff75c2d657146cf8fe646084439fd0f (patch)
tree6c098648df6a4da447aa305420709037115f81f0 /launcher/ui
parent17bbfe8d8951ddc7acca0222c6d2e38fb29eef25 (diff)
parentcc13310083fe8d0c3bb423fa3e31db7b1f7b25e1 (diff)
downloadPrismLauncher-ff9f3cb31ff75c2d657146cf8fe646084439fd0f.tar.gz
PrismLauncher-ff9f3cb31ff75c2d657146cf8fe646084439fd0f.tar.bz2
PrismLauncher-ff9f3cb31ff75c2d657146cf8fe646084439fd0f.zip
fix conflicts with develop
Diffstat (limited to 'launcher/ui')
-rw-r--r--launcher/ui/MainWindow.cpp51
-rw-r--r--launcher/ui/dialogs/ModDownloadDialog.cpp4
-rw-r--r--launcher/ui/dialogs/ModDownloadDialog.h4
-rw-r--r--launcher/ui/dialogs/NewInstanceDialog.cpp2
-rw-r--r--launcher/ui/dialogs/SkinUploadDialog.cpp3
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.cpp19
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.ui29
-rw-r--r--launcher/ui/pages/instance/ScreenshotsPage.cpp27
-rw-r--r--launcher/ui/pages/instance/ScreenshotsPage.h1
-rw-r--r--launcher/ui/pages/modplatform/ImportPage.cpp11
-rw-r--r--launcher/ui/pages/modplatform/ImportPage.ui78
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp43
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h43
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp82
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h61
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp291
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.h123
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp241
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.h60
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui112
20 files changed, 1184 insertions, 101 deletions
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index f34cf1ab..f016dc76 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -238,6 +238,9 @@ public:
TranslatedAction actionREDDIT;
TranslatedAction actionAbout;
+ TranslatedAction actionNoAccountsAdded;
+ TranslatedAction actionNoDefaultAccount;
+
QVector<TranslatedToolButton *> all_toolbuttons;
QWidget *centralWidget = nullptr;
@@ -746,6 +749,9 @@ public:
// disabled until we have an instance selected
instanceToolBar->setEnabled(false);
instanceToolBar->setMovable(true);
+ // Qt doesn't like vertical moving toolbars, so we have to force them...
+ // See https://github.com/PolyMC/PolyMC/issues/493
+ connect(instanceToolBar, &QToolBar::orientationChanged, [=](Qt::Orientation){ instanceToolBar->setOrientation(Qt::Vertical); });
instanceToolBar->setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea);
instanceToolBar->setToolButtonStyle(Qt::ToolButtonTextOnly);
instanceToolBar->setFloatable(false);
@@ -828,7 +834,7 @@ public:
QMetaObject::connectSlotsByName(MainWindow);
} // setupUi
- void retranslateUi(QMainWindow *MainWindow)
+ void retranslateUi(MainWindow *MainWindow)
{
QString winTitle = tr("%1 - Version %2", "Launcher - Version X").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString());
MainWindow->setWindowTitle(winTitle);
@@ -848,6 +854,12 @@ public:
// submenu buttons
foldersMenuButton->setText(tr("Folders"));
helpMenuButton->setText(tr("Help"));
+
+ // playtime counter
+ if (MainWindow->m_statusCenter)
+ {
+ MainWindow->updateStatusCenter();
+ }
} // retranslateUi
};
@@ -950,6 +962,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
ui->mainToolBar->addWidget(spacer);
accountMenu = new QMenu(this);
+ // Use undocumented property... https://stackoverflow.com/questions/7121718/create-a-scrollbar-in-a-submenu-qt
+ accountMenu->setStyleSheet("QMenu { menu-scrollable: 1; }");
repopulateAccountsMenu();
@@ -1252,10 +1266,14 @@ void MainWindow::repopulateAccountsMenu()
if (accounts->count() <= 0)
{
- QAction *action = new QAction(tr("No accounts added!"), this);
- action->setEnabled(false);
- accountMenu->addAction(action);
- ui->profileMenu->addAction(action);
+ ui->all_actions.removeAll(&ui->actionNoAccountsAdded);
+ ui->actionNoAccountsAdded = TranslatedAction(this);
+ ui->actionNoAccountsAdded->setObjectName(QStringLiteral("actionNoAccountsAdded"));
+ ui->actionNoAccountsAdded.setTextId(QT_TRANSLATE_NOOP("MainWindow", "No accounts added!"));
+ ui->actionNoAccountsAdded->setEnabled(false);
+ accountMenu->addAction(ui->actionNoAccountsAdded);
+ ui->profileMenu->addAction(ui->actionNoAccountsAdded);
+ ui->all_actions.append(&ui->actionNoAccountsAdded);
}
else
{
@@ -1295,18 +1313,23 @@ void MainWindow::repopulateAccountsMenu()
accountMenu->addSeparator();
ui->profileMenu->addSeparator();
- QAction *action = new QAction(tr("No Default Account"), this);
- action->setCheckable(true);
- action->setIcon(APPLICATION->getThemedIcon("noaccount"));
- action->setData(-1);
- action->setShortcut(QKeySequence(tr("Ctrl+0")));
+ ui->all_actions.removeAll(&ui->actionNoDefaultAccount);
+ ui->actionNoDefaultAccount = TranslatedAction(this);
+ ui->actionNoDefaultAccount->setObjectName(QStringLiteral("actionNoDefaultAccount"));
+ ui->actionNoDefaultAccount.setTextId(QT_TRANSLATE_NOOP("MainWindow", "No Default Account"));
+ ui->actionNoDefaultAccount->setCheckable(true);
+ ui->actionNoDefaultAccount->setIcon(APPLICATION->getThemedIcon("noaccount"));
+ ui->actionNoDefaultAccount->setData(-1);
+ ui->actionNoDefaultAccount->setShortcut(QKeySequence(tr("Ctrl+0")));
if (!defaultAccount) {
- action->setChecked(true);
+ ui->actionNoDefaultAccount->setChecked(true);
}
- accountMenu->addAction(action);
- ui->profileMenu->addAction(action);
- connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount()));
+ accountMenu->addAction(ui->actionNoDefaultAccount);
+ ui->profileMenu->addAction(ui->actionNoDefaultAccount);
+ connect(ui->actionNoDefaultAccount, SIGNAL(triggered(bool)), SLOT(changeActiveAccount()));
+ ui->all_actions.append(&ui->actionNoDefaultAccount);
+ ui->actionNoDefaultAccount.retranslate();
accountMenu->addSeparator();
ui->profileMenu->addSeparator();
diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp
index d02ea476..305e85c0 100644
--- a/launcher/ui/dialogs/ModDownloadDialog.cpp
+++ b/launcher/ui/dialogs/ModDownloadDialog.cpp
@@ -13,7 +13,7 @@
#include <QDialogButtonBox>
#include "ui/widgets/PageContainer.h"
-#include "ui/pages/modplatform/modrinth/ModrinthPage.h"
+#include "ui/pages/modplatform/modrinth/ModrinthModPage.h"
#include "ModDownloadTask.h"
@@ -98,7 +98,7 @@ void ModDownloadDialog::accept()
QList<BasePage *> ModDownloadDialog::getPages()
{
- modrinthPage = new ModrinthPage(this, m_instance);
+ modrinthPage = new ModrinthModPage(this, m_instance);
flameModPage = new FlameModPage(this, m_instance);
return
{
diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h
index 309d89d0..782dc361 100644
--- a/launcher/ui/dialogs/ModDownloadDialog.h
+++ b/launcher/ui/dialogs/ModDownloadDialog.h
@@ -16,7 +16,7 @@ class ModDownloadDialog;
class PageContainer;
class QDialogButtonBox;
-class ModrinthPage;
+class ModrinthModPage;
class ModDownloadDialog : public QDialog, public BasePageProvider
{
@@ -50,7 +50,7 @@ private:
QVBoxLayout *m_verticalLayout = nullptr;
- ModrinthPage *modrinthPage = nullptr;
+ ModrinthModPage *modrinthPage = nullptr;
FlameModPage *flameModPage = nullptr;
QHash<QString, ModDownloadTask*> modTask;
BaseInstance *m_instance;
diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp
index b402839c..05ea091d 100644
--- a/launcher/ui/dialogs/NewInstanceDialog.cpp
+++ b/launcher/ui/dialogs/NewInstanceDialog.cpp
@@ -39,6 +39,7 @@
#include "ui/pages/modplatform/legacy_ftb/Page.h"
#include "ui/pages/modplatform/flame/FlamePage.h"
#include "ui/pages/modplatform/ImportPage.h"
+#include "ui/pages/modplatform/modrinth/ModrinthPage.h"
#include "ui/pages/modplatform/technic/TechnicPage.h"
@@ -134,6 +135,7 @@ QList<BasePage *> NewInstanceDialog::getPages()
flamePage,
new FtbPage(this),
new LegacyFTB::Page(this),
+ new ModrinthPage(this),
technicPage
};
}
diff --git a/launcher/ui/dialogs/SkinUploadDialog.cpp b/launcher/ui/dialogs/SkinUploadDialog.cpp
index 6a5a324f..8d137afc 100644
--- a/launcher/ui/dialogs/SkinUploadDialog.cpp
+++ b/launcher/ui/dialogs/SkinUploadDialog.cpp
@@ -100,7 +100,8 @@ void SkinUploadDialog::on_buttonBox_accepted()
void SkinUploadDialog::on_skinBrowseBtn_clicked()
{
- QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), "*.png");
+ auto filter = QMimeDatabase().mimeTypeForName("image/png").filterString();
+ QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), filter);
if (raw_path.isEmpty() || !QFileInfo::exists(raw_path))
{
return;
diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp
index a48c4d69..b4562843 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp
@@ -101,6 +101,20 @@ void InstanceSettingsPage::applySettings()
{
SettingsObject::Lock lock(m_settings);
+ // Miscellaneous
+ bool miscellaneous = ui->miscellaneousSettingsBox->isChecked();
+ m_settings->set("OverrideMiscellaneous", miscellaneous);
+ if (miscellaneous)
+ {
+ m_settings->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked());
+ m_settings->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked());
+ }
+ else
+ {
+ m_settings->reset("CloseAfterLaunch");
+ m_settings->reset("QuitAfterGameStop");
+ }
+
// Console
bool console = ui->consoleSettingsBox->isChecked();
m_settings->set("OverrideConsole", console);
@@ -247,6 +261,11 @@ void InstanceSettingsPage::applySettings()
void InstanceSettingsPage::loadSettings()
{
+ // Miscellaneous
+ ui->miscellaneousSettingsBox->setChecked(m_settings->get("OverrideMiscellaneous").toBool());
+ ui->closeAfterLaunchCheck->setChecked(m_settings->get("CloseAfterLaunch").toBool());
+ ui->quitAfterGameStopCheck->setChecked(m_settings->get("QuitAfterGameStop").toBool());
+
// Console
ui->consoleSettingsBox->setChecked(m_settings->get("OverrideConsole").toBool());
ui->showConsoleCheck->setChecked(m_settings->get("ShowConsole").toBool());
diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui
index 5db2d147..cb66b3ce 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.ui
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui
@@ -350,6 +350,35 @@
</widget>
</item>
<item>
+ <widget class="QGroupBox" name="miscellaneousSettingsBox">
+ <property name="title">
+ <string>Miscellaneous</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_12">
+ <item>
+ <widget class="QCheckBox" name="closeAfterLaunchCheck">
+ <property name="text">
+ <string>Close the launcher after game window opens</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="quitAfterGameStopCheck">
+ <property name="text">
+ <string>Quit the launcher after game window closes</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
<spacer name="verticalSpacerMinecraft_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp
index e694ebe3..2cf17b32 100644
--- a/launcher/ui/pages/instance/ScreenshotsPage.cpp
+++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp
@@ -251,7 +251,7 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget *parent)
m_model.reset(new QFileSystemModel());
m_filterModel.reset(new FilterModel());
m_filterModel->setSourceModel(m_model.get());
- m_model->setFilter(QDir::Files | QDir::Writable | QDir::Readable);
+ m_model->setFilter(QDir::Files);
m_model->setReadOnly(false);
m_model->setNameFilters({"*.png"});
m_model->setNameFilterDisables(false);
@@ -343,6 +343,29 @@ void ScreenshotsPage::onItemActivated(QModelIndex index)
DesktopServices::openFile(info.absoluteFilePath());
}
+void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection &selected)
+{
+ bool allReadable = !selected.isEmpty();
+ bool allWritable = !selected.isEmpty();
+
+ for (auto index : selected.indexes())
+ {
+ if (!index.isValid())
+ break;
+ auto info = m_model->fileInfo(index);
+ if (!info.isReadable())
+ allReadable = false;
+ if (!info.isWritable())
+ allWritable = false;
+ }
+
+ ui->actionUpload->setEnabled(allReadable);
+ ui->actionCopy_Image->setEnabled(allReadable);
+ ui->actionCopy_File_s->setEnabled(allReadable);
+ ui->actionDelete->setEnabled(allWritable);
+ ui->actionRename->setEnabled(allWritable);
+}
+
void ScreenshotsPage::on_actionView_Folder_triggered()
{
DesktopServices::openDirectory(m_folder, true);
@@ -503,6 +526,8 @@ void ScreenshotsPage::openedImpl()
if(idx.isValid())
{
ui->listView->setModel(m_filterModel.get());
+ connect(ui->listView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ScreenshotsPage::onCurrentSelectionChanged);
+ onCurrentSelectionChanged(ui->listView->selectionModel()->selection()); // set initial button enable states
ui->listView->setRootIndex(m_filterModel->mapFromSource(idx));
}
else
diff --git a/launcher/ui/pages/instance/ScreenshotsPage.h b/launcher/ui/pages/instance/ScreenshotsPage.h
index 50cf1a17..c22706af 100644
--- a/launcher/ui/pages/instance/ScreenshotsPage.h
+++ b/launcher/ui/pages/instance/ScreenshotsPage.h
@@ -100,6 +100,7 @@ private slots:
void on_actionRename_triggered();
void on_actionView_Folder_triggered();
void onItemActivated(QModelIndex);
+ void onCurrentSelectionChanged(const QItemSelection &selected);
void ShowContextMenu(const QPoint &pos);
private:
diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp
index 487bf77b..c7bc13d8 100644
--- a/launcher/ui/pages/modplatform/ImportPage.cpp
+++ b/launcher/ui/pages/modplatform/ImportPage.cpp
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -109,7 +110,11 @@ void ImportPage::updateState()
{
// FIXME: actually do some validation of what's inside here... this is fake AF
QFileInfo fi(input);
- if(fi.exists() && fi.suffix() == "zip")
+ // mrpack is a modrinth pack
+
+ // Allow non-latin people to use ZIP files!
+ auto zip = QMimeDatabase().mimeTypeForUrl(url).suffixes().contains("zip");
+ if(fi.exists() && (zip || fi.suffix() == "mrpack"))
{
QFileInfo fi(url.fileName());
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url));
@@ -143,7 +148,9 @@ void ImportPage::setUrl(const QString& url)
void ImportPage::on_modpackBtn_clicked()
{
- const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), tr("Zip (*.zip)"));
+ auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString();
+ filter += ";;" + tr("Modrinth pack (*.mrpack)");
+ const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), filter);
if (url.isValid())
{
if (url.isLocalFile())
diff --git a/launcher/ui/pages/modplatform/ImportPage.ui b/launcher/ui/pages/modplatform/ImportPage.ui
index eb63cbe9..77bc5da5 100644
--- a/launcher/ui/pages/modplatform/ImportPage.ui
+++ b/launcher/ui/pages/modplatform/ImportPage.ui
@@ -11,28 +11,75 @@
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
- <item row="1" column="1">
- <widget class="QPushButton" name="modpackBtn">
- <property name="text">
- <string>Browse</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
+ <item row="2" column="0">
<widget class="QLineEdit" name="modpackEdit">
<property name="placeholderText">
<string notr="true">http://</string>
</property>
</widget>
</item>
- <item row="0" column="0" colspan="2">
- <widget class="QLabel" name="modpackLabel">
+ <item row="2" column="1">
+ <widget class="QPushButton" name="modpackBtn">
<property name="text">
- <string>Local file or link to a direct download:</string>
+ <string>Browse</string>
</property>
</widget>
</item>
- <item row="2" column="0" colspan="2">
+ <item row="3" column="0" rowspan="2" colspan="2">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>The following file types are implemented (both for local files and URLs):</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>- Curseforge modpacks (ZIP)</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>- Modrinth modpacks (ZIP and mrpack)</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>- PolyMC / MultiMC exported instances (ZIP)</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>- Technic modpacks (ZIP)</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="5" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -45,6 +92,13 @@
</property>
</spacer>
</item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QLabel" name="modpackLabel">
+ <property name="text">
+ <string>Local file or link to a direct download:</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
<resources/>
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp
new file mode 100644
index 00000000..1d9f4d60
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "ModrinthModModel.h"
+
+#include "modplatform/modrinth/ModrinthPackIndex.h"
+
+namespace Modrinth {
+
+// NOLINTNEXTLINE(modernize-avoid-c-arrays)
+const char* ListModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" };
+
+void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
+{
+ Modrinth::loadIndexedPack(m, obj);
+}
+
+void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
+{
+ Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance);
+}
+
+auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
+{
+ return obj.object().value("hits").toArray();
+}
+
+} // namespace Modrinth
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h
new file mode 100644
index 00000000..ae7b0bdd
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "ModrinthModPage.h"
+
+namespace Modrinth {
+
+class ListModel : public ModPlatform::ListModel {
+ Q_OBJECT
+
+ public:
+ ListModel(ModrinthModPage* parent) : ModPlatform::ListModel(parent){};
+ ~ListModel() override = default;
+
+ private:
+ void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
+ void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override;
+
+ auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
+
+ // NOLINTNEXTLINE(modernize-avoid-c-arrays)
+ static const char* sorts[5];
+ inline auto getSorts() const -> const char** override { return sorts; };
+};
+
+} // namespace Modrinth
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp
new file mode 100644
index 00000000..d3a1f859
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ModrinthModPage.h"
+#include "modplatform/modrinth/ModrinthAPI.h"
+#include "ui_ModPage.h"
+
+#include "ModrinthModModel.h"
+#include "ui/dialogs/ModDownloadDialog.h"
+
+ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance)
+ : ModPage(dialog, instance, new ModrinthAPI())
+{
+ listModel = new Modrinth::ListModel(this);
+ ui->packView->setModel(listModel);
+
+ // index is used to set the sorting with the modrinth api
+ ui->sortByBox->addItem(tr("Sort by Relevance"));
+ 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"));
+
+ // 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 constructor...
+ connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
+ connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthModPage::onSelectionChanged);
+ connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthModPage::onVersionSelectionChanged);
+ connect(ui->modSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onModSelected);
+}
+
+auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader) const -> bool
+{
+ auto loaderStrings = ModrinthAPI::getModLoaderStrings(loader);
+
+ auto loaderCompatible = false;
+ for (auto remoteLoader : ver.loaders)
+ {
+ if (loaderStrings.contains(remoteLoader)) {
+ loaderCompatible = true;
+ break;
+ }
+ }
+ return ver.mcVersion.contains(mineVer) && loaderCompatible;
+}
+
+// I don't know why, but doing this on the parent class makes it so that
+// other mod providers start loading before being selected, at least with
+// my Qt, so we need to implement this in every derived class...
+auto ModrinthModPage::shouldDisplay() const -> bool { return true; }
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h
new file mode 100644
index 00000000..b1e72bfe
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "modplatform/ModAPI.h"
+#include "ui/pages/modplatform/ModPage.h"
+
+#include "modplatform/modrinth/ModrinthAPI.h"
+
+class ModrinthModPage : public ModPage {
+ Q_OBJECT
+
+ public:
+ explicit ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance);
+ ~ModrinthModPage() override = default;
+
+ inline auto displayName() const -> QString override { return "Modrinth"; }
+ inline auto icon() const -> QIcon override { return APPLICATION->getThemedIcon("modrinth"); }
+ inline auto id() const -> QString override { return "modrinth"; }
+ inline auto helpPage() const -> QString override { return "Mod-platform"; }
+
+ inline auto debugName() const -> QString override { return "Modrinth"; }
+ inline auto metaEntryBase() const -> QString override { return "ModrinthPacks"; };
+
+ auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader = ModAPI::Unspecified) const -> bool override;
+
+ auto shouldDisplay() const -> bool override;
+};
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
index b788860a..7cacf37a 100644