[Architecture] Kotlin VanillaPacksTab (#717)

* Rebase onto master

* Commmit-work

* Use Kotlin Flow instead of RXJava

I wanted to maintain the code as java as much as possible, but RXJava
 is a pain to use. Now that it is in Kotlin, it is much nicer to
 write.

* Update CHANGELOG

* Rename .java to .kt

* Rewrite VanillaPacksTab with Kotlin & ViewModel

* Add localization

* Reimplement legacy fabric

* Fix misedited string

* Use apollo coroutines support in VanillaPacksViewModel

* FIx legacy fabric set function

* Add logs to VanillaPacksViewModel

* Fix minecraft version selection mechanic

* Fix version type selection mechanic

* Add way to sync selectedMinecraftVersion with GUI

* Format VanillaPacksViewModel.kt

* Readd logs & Simplify loader loading

* Fix mod loader auto selection

* Update CHANGELOG.md

* Update CHANGELOG.md

* Back to mainline

* Back to mainline

* Move MCVersionRow to data directory

* Update documentation in IVanillaPacksViewModel

* Attach visibility control to loader button UI

* Fix showLegacyFabricOption loader not using correct key

* Remove reset TODO from VanillaPacksViewModel
This commit is contained in:
Doomsdayrs 2023-02-12 21:35:26 -05:00 committed by GitHub
parent 592556070a
commit 591ec36136
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 1681 additions and 941 deletions

6
.idea/kotlinc.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.8.0" />
</component>
</project>

View file

@ -16,3 +16,4 @@ This changelog only contains the changes that are unreleased. For changes for in
- Don't allow installing non modpacks by ID in pack browser tabs
### Misc
- Implement view model for VanillaPacksTab [#717]

View file

@ -27,6 +27,7 @@ plugins {
id 'de.undercouch.download' version '5.1.0'
id 'com.github.johnrengelman.shadow' version '7.1.2'
id 'com.github.ben-manes.versions' version '0.42.0'
id "org.jetbrains.kotlin.jvm" version "1.8.0"
id 'com.apollographql.apollo' version '2.5.14'
}
@ -84,6 +85,11 @@ dependencies {
implementation 'com.github.hypfvieh:dbus-java:3.3.1'
implementation 'com.apollographql.apollo:apollo-runtime:2.5.14'
implementation 'com.apollographql.apollo:apollo-http-cache:2.5.14'
implementation 'com.apollographql.apollo:apollo-coroutines-support:2.5.14'
// Kotlin
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.6.4")
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:4.6.1'

View file

@ -0,0 +1,25 @@
/*
* ATLauncher - https://github.com/ATLauncher/ATLauncher
* Copyright (C) 2013-2022 ATLauncher
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.atlauncher.data
/**
* Specifies a row to display in the VanillaPacksTab
*
* @see [com.atlauncher.gui.tabs.VanillaPacksTab]
*/
class MCVersionRow(val id: String, val date: String, val type: String)

View file

@ -1,941 +0,0 @@
/*
* ATLauncher - https://github.com/ATLauncher/ATLauncher
* Copyright (C) 2013-2022 ATLauncher
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.atlauncher.gui.tabs;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumnModel;
import org.jetbrains.annotations.NotNull;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.mini2Dx.gettext.GetText;
import com.apollographql.apollo.ApolloCall;
import com.apollographql.apollo.api.Response;
import com.apollographql.apollo.api.cache.http.HttpCachePolicy;
import com.apollographql.apollo.api.cache.http.HttpCachePolicy.FetchStrategy;
import com.apollographql.apollo.exception.ApolloException;
import com.atlauncher.App;
import com.atlauncher.builders.HTMLBuilder;
import com.atlauncher.constants.UIConstants;
import com.atlauncher.data.installables.Installable;
import com.atlauncher.data.installables.VanillaInstallable;
import com.atlauncher.data.minecraft.VersionManifestVersion;
import com.atlauncher.data.minecraft.VersionManifestVersionType;
import com.atlauncher.data.minecraft.loaders.LoaderType;
import com.atlauncher.data.minecraft.loaders.LoaderVersion;
import com.atlauncher.data.minecraft.loaders.fabric.FabricLoader;
import com.atlauncher.data.minecraft.loaders.forge.ForgeLoader;
import com.atlauncher.data.minecraft.loaders.legacyfabric.LegacyFabricLoader;
import com.atlauncher.data.minecraft.loaders.quilt.QuiltLoader;
import com.atlauncher.exceptions.InvalidMinecraftVersion;
import com.atlauncher.graphql.GetLoaderVersionsForMinecraftVersionQuery;
import com.atlauncher.managers.ConfigManager;
import com.atlauncher.managers.DialogManager;
import com.atlauncher.managers.InstanceManager;
import com.atlauncher.managers.LogManager;
import com.atlauncher.managers.MinecraftManager;
import com.atlauncher.network.GraphqlClient;
import com.atlauncher.utils.ComboItem;
import com.atlauncher.utils.Pair;
import com.atlauncher.utils.Utils;
@SuppressWarnings("serial")
public final class VanillaPacksTab extends JPanel implements Tab {
private final List<VersionManifestVersionType> minecraftVersionTypeFilters = new ArrayList<>(
Arrays.asList(VersionManifestVersionType.RELEASE));
private String selectedMinecraftVersion = null;
private final JTextField nameField = new JTextField(32);
private boolean nameFieldDirty = false;
private final JTextArea descriptionField = new JTextArea(2, 40);
private boolean descriptionFieldDirty = false;
private final JCheckBox minecraftVersionReleasesFilterCheckbox = new JCheckBox(GetText.tr("Releases"));
private final JCheckBox minecraftVersionExperimentsFilterCheckbox = new JCheckBox(GetText.tr("Experiments"));
private final JCheckBox minecraftVersionSnapshotsFilterCheckbox = new JCheckBox(GetText.tr("Snapshots"));
private final JCheckBox minecraftVersionBetasFilterCheckbox = new JCheckBox(GetText.tr("Betas"));
private final JCheckBox minecraftVersionAlphasFilterCheckbox = new JCheckBox(GetText.tr("Alphas"));
private JTable minecraftVersionTable;
private DefaultTableModel minecraftVersionTableModel;
private final ButtonGroup loaderTypeButtonGroup = new ButtonGroup();
private final JRadioButton loaderTypeNoneRadioButton = new JRadioButton(GetText.tr("None"));
private final JRadioButton loaderTypeFabricRadioButton = new JRadioButton("Fabric");
private final JRadioButton loaderTypeForgeRadioButton = new JRadioButton("Forge");
private final JRadioButton loaderTypeLegacyFabricRadioButton = new JRadioButton("Legacy Fabric");
private final JRadioButton loaderTypeQuiltRadioButton = new JRadioButton("Quilt");
private final JComboBox<ComboItem<LoaderVersion>> loaderVersionsDropDown = new JComboBox<>();
private final JButton createServerButton = new JButton(GetText.tr("Create Server"));
private final JButton createInstanceButton = new JButton(GetText.tr("Create Instance"));
public VanillaPacksTab() {
super(new BorderLayout());
setName("vanillaPacksPanel");
setupMainPanel();
setupBottomPanel();
}
private void setupMainPanel() {
JPanel mainPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
// Name
gbc.gridx = 0;
gbc.gridy = 0;
gbc.insets = UIConstants.LABEL_INSETS;
gbc.anchor = GridBagConstraints.EAST;
JLabel nameLabel = new JLabel(GetText.tr("Instance Name") + ":");
mainPanel.add(nameLabel, gbc);
gbc.gridx++;
gbc.insets = UIConstants.FIELD_INSETS;
gbc.anchor = GridBagConstraints.BASELINE_LEADING;
nameField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void changedUpdate(DocumentEvent e) {
checkDirty(e);
}
@Override
public void insertUpdate(DocumentEvent e) {
checkDirty(e);
}
@Override
public void removeUpdate(DocumentEvent e) {
checkDirty(e);
}
private void checkDirty(DocumentEvent e) {
if (selectedMinecraftVersion == null) {
return;
}
String currentValue = nameField.getText();
LoaderType selectedLoader = getSelectedLoader();
// if the name is the same as the default is, then we're not dirty
nameFieldDirty = !(currentValue.equals(String.format("Minecraft %s", selectedMinecraftVersion))
|| (selectedLoader != null && currentValue.equals(
String.format("Minecraft %s with %s", selectedMinecraftVersion, selectedLoader))));
}
});
mainPanel.add(nameField, gbc);
// Description
gbc.gridx = 0;
gbc.gridy++;
gbc.insets = UIConstants.LABEL_INSETS;
gbc.anchor = GridBagConstraints.BASELINE_TRAILING;
JLabel descriptionLabel = new JLabel(GetText.tr("Description") + ":");
mainPanel.add(descriptionLabel, gbc);
gbc.gridx++;
gbc.insets = UIConstants.FIELD_INSETS;
gbc.anchor = GridBagConstraints.BASELINE_LEADING;
JScrollPane descriptionScrollPane = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
descriptionScrollPane.setPreferredSize(new Dimension(450, 80));
descriptionScrollPane.setViewportView(descriptionField);
descriptionField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void changedUpdate(DocumentEvent e) {
checkDirty(e);
}
@Override
public void insertUpdate(DocumentEvent e) {
checkDirty(e);
}
@Override
public void removeUpdate(DocumentEvent e) {
checkDirty(e);
}
private void checkDirty(DocumentEvent e) {
if (selectedMinecraftVersion == null) {
return;
}
String currentValue = descriptionField.getText();
LoaderType selectedLoader = getSelectedLoader();
// if the description is the same as the default is, then we're not dirty
descriptionFieldDirty = !(currentValue.equals(String.format("Minecraft %s", selectedMinecraftVersion))
|| (selectedLoader != null && currentValue.equals(
String.format("Minecraft %s with %s", selectedMinecraftVersion, selectedLoader))));
}
});
mainPanel.add(descriptionScrollPane, gbc);
// Minecraft Version
gbc.gridx = 0;
gbc.gridy += 2;
gbc.insets = UIConstants.LABEL_INSETS;
gbc.anchor = GridBagConstraints.NORTHEAST;
JPanel minecraftVersionPanel = new JPanel();
minecraftVersionPanel.setLayout(new BoxLayout(minecraftVersionPanel, BoxLayout.Y_AXIS));
JLabel minecraftVersionLabel = new JLabel(GetText.tr("Minecraft Version") + ":");
minecraftVersionPanel.add(minecraftVersionLabel);
minecraftVersionPanel.add(Box.createVerticalStrut(20));
JPanel minecraftVersionFilterPanel = new JPanel();
minecraftVersionFilterPanel.setLayout(new BoxLayout(minecraftVersionFilterPanel, BoxLayout.Y_AXIS));
JLabel minecraftVersionFilterLabel = new JLabel(GetText.tr("Filter"));
minecraftVersionFilterLabel.setFont(App.THEME.getBoldFont());
minecraftVersionFilterPanel.add(minecraftVersionFilterLabel);
minecraftVersionReleasesFilterCheckbox.setSelected(true);
minecraftVersionReleasesFilterCheckbox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (minecraftVersionReleasesFilterCheckbox.isSelected()) {
minecraftVersionTypeFilters.add(VersionManifestVersionType.RELEASE);
} else {
minecraftVersionTypeFilters.remove(VersionManifestVersionType.RELEASE);
}
reloadMinecraftVersionsTable();
}
});
if (ConfigManager.getConfigItem("minecraft.release.enabled", true) == true) {
minecraftVersionFilterPanel.add(minecraftVersionReleasesFilterCheckbox);
}
minecraftVersionExperimentsFilterCheckbox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (minecraftVersionExperimentsFilterCheckbox.isSelected()) {
minecraftVersionTypeFilters.add(VersionManifestVersionType.EXPERIMENT);
} else {
minecraftVersionTypeFilters.remove(VersionManifestVersionType.EXPERIMENT);
}
reloadMinecraftVersionsTable();
}
});
if (ConfigManager.getConfigItem("minecraft.experiment.enabled", true) == true) {
minecraftVersionFilterPanel.add(minecraftVersionExperimentsFilterCheckbox);
}
minecraftVersionSnapshotsFilterCheckbox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (minecraftVersionSnapshotsFilterCheckbox.isSelected()) {
minecraftVersionTypeFilters.add(VersionManifestVersionType.SNAPSHOT);
} else {
minecraftVersionTypeFilters.remove(VersionManifestVersionType.SNAPSHOT);
}
reloadMinecraftVersionsTable();
}
});
if (ConfigManager.getConfigItem("minecraft.snapshot.enabled", true) == true) {
minecraftVersionFilterPanel.add(minecraftVersionSnapshotsFilterCheckbox);
}
minecraftVersionBetasFilterCheckbox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (minecraftVersionBetasFilterCheckbox.isSelected()) {
minecraftVersionTypeFilters.add(VersionManifestVersionType.OLD_BETA);
} else {
minecraftVersionTypeFilters.remove(VersionManifestVersionType.OLD_BETA);
}
reloadMinecraftVersionsTable();
}
});
if (ConfigManager.getConfigItem("minecraft.old_alpha.enabled", true) == true) {
minecraftVersionFilterPanel.add(minecraftVersionBetasFilterCheckbox);
}
minecraftVersionAlphasFilterCheckbox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (minecraftVersionAlphasFilterCheckbox.isSelected()) {
minecraftVersionTypeFilters.add(VersionManifestVersionType.OLD_ALPHA);
} else {
minecraftVersionTypeFilters.remove(VersionManifestVersionType.OLD_ALPHA);
}
reloadMinecraftVersionsTable();
}
});
if (ConfigManager.getConfigItem("minecraft.old_beta.enabled", true) == true) {
minecraftVersionFilterPanel.add(minecraftVersionAlphasFilterCheckbox);
}
minecraftVersionPanel.add(minecraftVersionFilterPanel);
mainPanel.add(minecraftVersionPanel, gbc);
gbc.gridx++;
gbc.insets = UIConstants.FIELD_INSETS;
gbc.anchor = GridBagConstraints.BASELINE_LEADING;
JScrollPane minecraftVersionScrollPane = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
minecraftVersionScrollPane.setPreferredSize(new Dimension(450, 300));
setupMinecraftVersionsTable();
reloadMinecraftVersionsTable();
minecraftVersionScrollPane.setViewportView(minecraftVersionTable);
mainPanel.add(minecraftVersionScrollPane, gbc);
// Loader Type
gbc.gridx = 0;
gbc.gridy++;
gbc.insets = UIConstants.LABEL_INSETS;
gbc.anchor = GridBagConstraints.EAST;
JLabel loaderTypeLabel = new JLabel(GetText.tr("Loader") + "?");
mainPanel.add(loaderTypeLabel, gbc);
gbc.gridx++;
gbc.insets = UIConstants.FIELD_INSETS;
gbc.anchor = GridBagConstraints.BASELINE_LEADING;
loaderTypeButtonGroup.add(loaderTypeNoneRadioButton);
loaderTypeButtonGroup.add(loaderTypeFabricRadioButton);
loaderTypeButtonGroup.add(loaderTypeForgeRadioButton);
loaderTypeButtonGroup.add(loaderTypeLegacyFabricRadioButton);
loaderTypeButtonGroup.add(loaderTypeQuiltRadioButton);
JPanel loaderTypePanel = new JPanel(new FlowLayout());
loaderTypePanel.add(loaderTypeNoneRadioButton);
if (ConfigManager.getConfigItem("loaders.fabric.enabled", true) == true) {
loaderTypePanel.add(loaderTypeFabricRadioButton);
}
if (ConfigManager.getConfigItem("loaders.forge.enabled", true) == true) {
loaderTypePanel.add(loaderTypeForgeRadioButton);
}
if (ConfigManager.getConfigItem("loaders.legacyfabric.enabled", true) == true) {
loaderTypePanel.add(loaderTypeLegacyFabricRadioButton);
}
if (ConfigManager.getConfigItem("loaders.quilt.enabled", false) == true) {
loaderTypePanel.add(loaderTypeQuiltRadioButton);
}
loaderTypeNoneRadioButton.addActionListener(e -> {
selectedLoaderTypeChanged(null);
});
loaderTypeFabricRadioButton.addActionListener(e -> {
selectedLoaderTypeChanged(LoaderType.FABRIC);
});
loaderTypeForgeRadioButton.addActionListener(e -> {
selectedLoaderTypeChanged(LoaderType.FORGE);
});
loaderTypeLegacyFabricRadioButton.addActionListener(e -> {
selectedLoaderTypeChanged(LoaderType.LEGACY_FABRIC);
});
loaderTypeQuiltRadioButton.addActionListener(e -> {
selectedLoaderTypeChanged(LoaderType.QUILT);
});
loaderTypeNoneRadioButton.setSelected(true);
mainPanel.add(loaderTypePanel, gbc);
// Loader Version
gbc.gridx = 0;
gbc.gridy++;
gbc.insets = UIConstants.LABEL_INSETS;
gbc.anchor = GridBagConstraints.BASELINE_TRAILING;
JLabel loaderVersionLabel = new JLabel(GetText.tr("Loader Version") + ":");
mainPanel.add(loaderVersionLabel, gbc);
gbc.gridx++;
gbc.insets = UIConstants.FIELD_INSETS;
gbc.anchor = GridBagConstraints.BASELINE_LEADING;
loaderVersionsDropDown.setEnabled(false);
loaderVersionsDropDown.addItem(new ComboItem<LoaderVersion>(null, GetText.tr("Select Loader First")));
mainPanel.add(loaderVersionsDropDown, gbc);
add(mainPanel, BorderLayout.CENTER);
}
private void setupMinecraftVersionsTable() {
minecraftVersionTableModel = new DefaultTableModel(new String[][] {},
new String[] { GetText.tr("Version"), GetText.tr("Released"), GetText.tr("Type") }) {
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return false;
}
};
minecraftVersionTable = new JTable(minecraftVersionTableModel);
minecraftVersionTable.getTableHeader().setReorderingAllowed(false);
ListSelectionModel sm = minecraftVersionTable.getSelectionModel();
sm.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
return;
}
ListSelectionModel lsm = (ListSelectionModel) e.getSource();
int minIndex = lsm.getMinSelectionIndex();
int maxIndex = lsm.getMaxSelectionIndex();
for (int i = minIndex; i <= maxIndex; i++) {
if (lsm.isSelectedIndex(i)) {
selectedMinecraftVersionChanged((String) minecraftVersionTableModel.getValueAt(i, 0));
}
}
}
});
TableColumnModel cm = minecraftVersionTable.getColumnModel();
cm.getColumn(0).setResizable(false);
cm.getColumn(1).setResizable(false);
cm.getColumn(1).setMaxWidth(200);
cm.getColumn(2).setResizable(false);
cm.getColumn(2).setMaxWidth(200);
minecraftVersionTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
minecraftVersionTable.setShowVerticalLines(false);
}
private void selectedMinecraftVersionChanged(String newSelectedMinecraftVersion) {
if (selectedMinecraftVersion != newSelectedMinecraftVersion) {
selectedMinecraftVersion = newSelectedMinecraftVersion;
String defaultValue = String.format("Minecraft %s", newSelectedMinecraftVersion);
try {
VersionManifestVersion version = MinecraftManager.getMinecraftVersion(newSelectedMinecraftVersion);
createServerButton.setVisible(version.hasServer());
} catch (InvalidMinecraftVersion ignored) {
createServerButton.setVisible(false);
}
if (!nameFieldDirty) {
nameField.setText(defaultValue);
}
if (!descriptionFieldDirty) {
descriptionField.setText(defaultValue);
}
loaderTypeFabricRadioButton.setVisible(
!ConfigManager.getConfigItem("loaders.fabric.disabledMinecraftVersions", new ArrayList<String>())
.contains(newSelectedMinecraftVersion));
loaderTypeForgeRadioButton.setVisible(
!ConfigManager.getConfigItem("loaders.forge.disabledMinecraftVersions", new ArrayList<String>())
.contains(newSelectedMinecraftVersion));
loaderTypeLegacyFabricRadioButton.setVisible(
!ConfigManager
.getConfigItem("loaders.legacyfabric.disabledMinecraftVersions", new ArrayList<String>())
.contains(newSelectedMinecraftVersion));
loaderTypeQuiltRadioButton.setVisible(
!ConfigManager.getConfigItem("loaders.quilt.disabledMinecraftVersions", new ArrayList<String>())
.contains(newSelectedMinecraftVersion));
// refresh the loader versions if we have one selected
LoaderType selectedLoaderType = getSelectedLoader();
if (selectedLoaderType != null) {
selectedLoaderTypeChanged(selectedLoaderType);
}
}
}
private LoaderType getSelectedLoader() {
if (loaderTypeFabricRadioButton.isSelected()) {
return LoaderType.FABRIC;
}
if (loaderTypeForgeRadioButton.isSelected()) {
return LoaderType.FORGE;
}
if (loaderTypeLegacyFabricRadioButton.isSelected()) {
return LoaderType.LEGACY_FABRIC;
}
if (loaderTypeQuiltRadioButton.isSelected()) {
return LoaderType.QUILT;
}
return null;
}
private void reloadMinecraftVersionsTable() {
// remove all rows
int rowCount = minecraftVersionTableModel.getRowCount();
if (rowCount > 0) {
for (int i = rowCount - 1; i >= 0; i--) {
minecraftVersionTableModel.removeRow(i);
}
}
List<VersionManifestVersion> minecraftVersions = MinecraftManager
.getFilteredMinecraftVersions(minecraftVersionTypeFilters);
DateTimeFormatter fmt = DateTimeFormat.forPattern(App.settings.dateFormat);
minecraftVersions.stream().forEach(mv -> {
minecraftVersionTableModel.addRow(new String[] { mv.id,
fmt.print(ISODateTimeFormat.dateTimeParser().parseDateTime(mv.releaseTime)), mv.type.toString() });
});
if (minecraftVersionTable.getRowCount() >= 1) {
// figure out which row to select
int newSelectedRow = 0;
if (selectedMinecraftVersion != null) {
Optional<VersionManifestVersion> versionToSelect = minecraftVersions.stream()
.filter(mv -> mv.id.equals(selectedMinecraftVersion)).findFirst();
if (versionToSelect.isPresent()) {
newSelectedRow = minecraftVersions.indexOf(versionToSelect.get());
}
}
minecraftVersionTable.setRowSelectionInterval(newSelectedRow, newSelectedRow);
}
// refresh the table
minecraftVersionTable.revalidate();
// update checkboxes so not all of them can be unchecked
minecraftVersionReleasesFilterCheckbox.setEnabled(
!(minecraftVersionReleasesFilterCheckbox.isSelected() && minecraftVersionTypeFilters.size() == 1));
minecraftVersionExperimentsFilterCheckbox.setEnabled(
!(minecraftVersionExperimentsFilterCheckbox.isSelected() && minecraftVersionTypeFilters.size() == 1));
minecraftVersionSnapshotsFilterCheckbox.setEnabled(
!(minecraftVersionSnapshotsFilterCheckbox.isSelected() && minecraftVersionTypeFilters.size() == 1));
minecraftVersionBetasFilterCheckbox.setEnabled(
!(minecraftVersionBetasFilterCheckbox.isSelected() && minecraftVersionTypeFilters.size() == 1));
minecraftVersionAlphasFilterCheckbox.setEnabled(
!(minecraftVersionAlphasFilterCheckbox.isSelected() && minecraftVersionTypeFilters.size() == 1));
}
private void selectedLoaderTypeChanged(LoaderType selectedLoader) {
loaderVersionsDropDown.removeAllItems();
loaderVersionsDropDown.setEnabled(false);
if (selectedLoader == null) {
// update the name and description fields if they're not dirty
String defaultNameFieldValue = String.format("Minecraft %s", selectedMinecraftVersion);
if (!nameFieldDirty) {
nameField.setText(defaultNameFieldValue);
}
if (!descriptionFieldDirty) {
descriptionField.setText(defaultNameFieldValue);
}
loaderVersionsDropDown.addItem(new ComboItem<LoaderVersion>(null, GetText.tr("Select Loader First")));
return;
}
loaderVersionsDropDown.addItem(new ComboItem<LoaderVersion>(null, GetText.tr("Getting Loader Versions")));
loaderTypeNoneRadioButton.setEnabled(false);
loaderTypeFabricRadioButton.setEnabled(false);
loaderTypeForgeRadioButton.setEnabled(false);
loaderTypeLegacyFabricRadioButton.setEnabled(false);
loaderTypeQuiltRadioButton.setEnabled(false);
loaderVersionsDropDown.setEnabled(false);
createServerButton.setEnabled(false);
createInstanceButton.setEnabled(false);
// Legacy Forge doesn't support servers easily
boolean enableCreateServers = selectedLoader != LoaderType.FORGE
|| !Utils.matchVersion(selectedMinecraftVersion, "1.5", true, true);
if (ConfigManager.getConfigItem("useGraphql.vanillaLoaderVersions", false) == true) {
GraphqlClient.apolloClient.query(new GetLoaderVersionsForMinecraftVersionQuery(selectedMinecraftVersion))
.toBuilder()
.httpCachePolicy(new HttpCachePolicy.Policy(FetchStrategy.CACHE_FIRST, 5, TimeUnit.MINUTES, false))
.build()
.enqueue(new ApolloCall.Callback<GetLoaderVersionsForMinecraftVersionQuery.Data>() {
@Override
public void onResponse(
@NotNull Response<GetLoaderVersionsForMinecraftVersionQuery.Data> response) {
List<LoaderVersion> loaderVersions = new ArrayList<>();
if (selectedLoader == LoaderType.FABRIC) {
List<String> disabledVersions = ConfigManager.getConfigItem(
"loaders.fabric.disabledVersions",
new ArrayList<String>());
loaderVersions.addAll(response.getData().loaderVersions().fabric().stream()
.filter(fv -> !disabledVersions.contains(fv.version()))
.map(version -> new LoaderVersion(version.version(), false, "Fabric"))
.collect(Collectors.toList()));
} else if (selectedLoader == LoaderType.FORGE) {
List<String> disabledVersions = ConfigManager.getConfigItem(
"loaders.forge.disabledVersions",
new ArrayList<String>());
loaderVersions.addAll(response.getData().loaderVersions().forge().stream()
.filter(fv -> !disabledVersions.contains(fv.version()))
.map(version -> {
LoaderVersion lv = new LoaderVersion(version.version(),
version.rawVersion(),
version.recommended(),
"Forge");
if (version.installerSha1Hash() != null
&& version.installerSize() != null) {
lv.downloadables.put("installer",
new Pair<String, Long>(version.installerSha1Hash(),
version.installerSize().longValue()));
}
if (version.universalSha1Hash() != null
&& version.universalSize() != null) {
lv.downloadables.put("universal",
new Pair<String, Long>(version.universalSha1Hash(),
version.universalSize().longValue()));
}
if (version.clientSha1Hash() != null && version.clientSize() != null) {
lv.downloadables.put("client",
new Pair<String, Long>(version.clientSha1Hash(),
version.clientSize().longValue()));
}
if (version.serverSha1Hash() != null && version.serverSize() != null) {
lv.downloadables.put("server",
new Pair<String, Long>(version.serverSha1Hash(),
version.serverSize().longValue()));
}
return lv;
})
.collect(Collectors.toList()));
} else if (selectedLoader == LoaderType.LEGACY_FABRIC) {
List<String> disabledVersions = ConfigManager.getConfigItem(
"loaders.legacyfabric.disabledVersions",
new ArrayList<String>());
loaderVersions.addAll(response.getData().loaderVersions().legacyfabric().stream()
.filter(fv -> !disabledVersions.contains(fv.version()))
.map(version -> new LoaderVersion(version.version(), false, "LegacyFabric"))
.collect(Collectors.toList()));
} else if (selectedLoader == LoaderType.QUILT) {
List<String> disabledVersions = ConfigManager.getConfigItem(
"loaders.quilt.disabledVersions",
new ArrayList<String>());
loaderVersions.addAll(response.getData().loaderVersions().quilt().stream()
.filter(fv -> !disabledVersions.contains(fv.version()))
.map(version -> new LoaderVersion(version.version(), false, "Quilt"))
.collect(Collectors.toList()));
}
if (loaderVersions.size() == 0) {
loaderVersionsDropDown.removeAllItems();
loaderVersionsDropDown
.addItem(new ComboItem<LoaderVersion>(null, GetText.tr("No Versions Found")));
loaderTypeNoneRadioButton.setEnabled(true);
loaderTypeFabricRadioButton.setEnabled(true);
loaderTypeForgeRadioButton.setEnabled(true);
loaderTypeLegacyFabricRadioButton.setEnabled(true);
loaderTypeQuiltRadioButton.setEnabled(true);
createServerButton.setEnabled(enableCreateServers);
createInstanceButton.setEnabled(true);
return;
}
int loaderVersionLength = 0;
// ensures that font width is taken into account
for (LoaderVersion version : loaderVersions) {
loaderVersionLength = Math.max(loaderVersionLength,
getFontMetrics(App.THEME.getNormalFont()).stringWidth(version.toString()) + 25);
}
loaderVersionsDropDown.removeAllItems();
loaderVersions.forEach(version -> loaderVersionsDropDown
.addItem(new ComboItem<LoaderVersion>(version, version.toString())));
if (selectedLoader == LoaderType.FORGE) {
Optional<LoaderVersion> recommendedVersion = loaderVersions.stream()
.filter(lv -> lv.recommended)
.findFirst();
if (recommendedVersion.isPresent()) {
loaderVersionsDropDown
.setSelectedIndex(loaderVersions.indexOf(recommendedVersion.get()));
}
}
// ensures that the dropdown is at least 200 px wide
loaderVersionLength = Math.max(200, loaderVersionLength);
// ensures that there is a maximum width of 400 px to prevent overflow
loaderVersionLength = Math.min(400, loaderVersionLength);
loaderVersionsDropDown.setPreferredSize(new Dimension(loaderVersionLength, 23));
loaderTypeNoneRadioButton.setEnabled(true);
loaderTypeFabricRadioButton.setEnabled(true);
loaderTypeForgeRadioButton.setEnabled(true);
loaderTypeLegacyFabricRadioButton.setEnabled(true);
loaderTypeQuiltRadioButton.setEnabled(true);
loaderVersionsDropDown.setEnabled(true);
createServerButton.setEnabled(enableCreateServers);
createInstanceButton.setEnabled(true);
// update the name and description fields if they're not dirty
String defaultNameFieldValue = String.format("Minecraft %s with %s",
selectedMinecraftVersion,
selectedLoader.toString());
if (!nameFieldDirty) {
nameField.setText(defaultNameFieldValue);
}
if (!descriptionFieldDirty) {
descriptionField.setText(defaultNameFieldValue);
}
}
@Override
public void onFailure(@NotNull ApolloException e) {
LogManager.logStackTrace("Error fetching loading versions", e);
loaderVersionsDropDown.removeAllItems();
loaderVersionsDropDown
.addItem(new ComboItem<LoaderVersion>(null, GetText.tr("Error Getting Versions")));
loaderTypeNoneRadioButton.setEnabled(true);
loaderTypeFabricRadioButton.setEnabled(true);
loaderTypeForgeRadioButton.setEnabled(true);
loaderTypeLegacyFabricRadioButton.setEnabled(true);
loaderTypeQuiltRadioButton.setEnabled(true);
createServerButton.setEnabled(enableCreateServers);
createInstanceButton.setEnabled(true);
return;
}
});
} else {
Runnable r = () -> {
List<LoaderVersion> loaderVersions = new ArrayList<>();
if (selectedLoader == LoaderType.FABRIC) {
loaderVersions.addAll(FabricLoader.getChoosableVersions(selectedMinecraftVersion));
} else if (selectedLoader == LoaderType.FORGE) {
loaderVersions.addAll(ForgeLoader.getChoosableVersions(selectedMinecraftVersion));
} else if (selectedLoader == LoaderType.LEGACY_FABRIC) {
loaderVersions.addAll(LegacyFabricLoader.getChoosableVersions(selectedMinecraftVersion));
} else if (selectedLoader == LoaderType.QUILT) {
loaderVersions.addAll(QuiltLoader.getChoosableVersions(selectedMinecraftVersion));
}
if (loaderVersions.size() == 0) {
loaderVersionsDropDown.removeAllItems();
loaderVersionsDropDown.addItem(new ComboItem<LoaderVersion>(null, GetText.tr("No Versions Found")));
loaderTypeNoneRadioButton.setEnabled(true);
loaderTypeFabricRadioButton.setEnabled(true);
loaderTypeForgeRadioButton.setEnabled(true);
loaderTypeLegacyFabricRadioButton.setEnabled(true);
loaderTypeQuiltRadioButton.setEnabled(true);
createServerButton.setEnabled(enableCreateServers);
createInstanceButton.setEnabled(true);
return;
}
int loaderVersionLength = 0;
// ensures that font width is taken into account
for (LoaderVersion version : loaderVersions) {
loaderVersionLength = Math.max(loaderVersionLength,
getFontMetrics(App.THEME.getNormalFont()).stringWidth(version.toString()) + 25);
}
loaderVersionsDropDown.removeAllItems();
loaderVersions.forEach(version -> loaderVersionsDropDown
.addItem(new ComboItem<LoaderVersion>(version, version.toString())));
if (selectedLoader == LoaderType.FORGE) {
Optional<LoaderVersion> recommendedVersion = loaderVersions.stream().filter(lv -> lv.recommended)
.findFirst();
if (recommendedVersion.isPresent()) {
loaderVersionsDropDown.setSelectedIndex(loaderVersions.indexOf(recommendedVersion.get()));
}
}
// ensures that the dropdown is at least 200 px wide
loaderVersionLength = Math.max(200, loaderVersionLength);
// ensures that there is a maximum width of 400 px to prevent overflow
loaderVersionLength = Math.min(400, loaderVersionLength);
loaderVersionsDropDown.setPreferredSize(new Dimension(loaderVersionLength, 23));
loaderTypeNoneRadioButton.setEnabled(true);
loaderTypeFabricRadioButton.setEnabled(true);
loaderTypeForgeRadioButton.setEnabled(true);
loaderTypeLegacyFabricRadioButton.setEnabled(true);
loaderTypeQuiltRadioButton.setEnabled(true);
loaderVersionsDropDown.setEnabled(true);
createServerButton.setEnabled(enableCreateServers);
createInstanceButton.setEnabled(true);
// update the name and description fields if they're not dirty
String defaultNameFieldValue = String.format("Minecraft %s with %s", selectedMinecraftVersion,
selectedLoader.toString());
if (!nameFieldDirty) {
nameField.setText(defaultNameFieldValue);
}
if (!descriptionFieldDirty) {
descriptionField.setText(defaultNameFieldValue);
}
};
new Thread(r).start();
}
}
private void setupBottomPanel() {
JPanel bottomPanel = new JPanel(new FlowLayout());
bottomPanel.add(createServerButton);
createServerButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// user has no instances, they may not be aware this is not how to play
if (InstanceManager.getInstances().size() == 0) {
int ret = DialogManager.yesNoDialog()
.setTitle(GetText.tr("Are you sure you want to create a server?"))
.setContent(new HTMLBuilder().center().text(GetText.tr(
"Creating a server won't allow you play Minecraft, it's for letting others play together.<br/><br/>If you just want to play Minecraft, you don't want to create a server, and instead will want to create an instance.<br/><br/>Are you sure you want to create a server?"))
.build())
.setType(DialogManager.QUESTION).show();
if (ret != 0) {
return;
}
}
install(true);
}
});
bottomPanel.add(createInstanceButton);
createInstanceButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
install(false);
}
});
add(bottomPanel, BorderLayout.SOUTH);
}
private void install(boolean isServer) {
Installable installable;
try {
LoaderVersion selectedLoaderVersion = ((ComboItem<LoaderVersion>) loaderVersionsDropDown.getSelectedItem())
.getValue();
installable = new VanillaInstallable(MinecraftManager.getMinecraftVersion(selectedMinecraftVersion),
selectedLoaderVersion, descriptionField.getText());
installable.instanceName = nameField.getText();
installable.isReinstall = false;
installable.isServer = isServer;
boolean success = installable.startInstall();
if (success) {
nameFieldDirty = false;
descriptionFieldDirty = false;
loaderTypeNoneRadioButton.setSelected(true);
selectedLoaderTypeChanged(null);
minecraftVersionTable.setRowSelectionInterval(0, 0);
}
} catch (InvalidMinecraftVersion e) {
LogManager.logStackTrace(e);
}
}
@Override
public String getTitle() {
return GetText.tr("Vanilla Packs");
}
@Override
public String getAnalyticsScreenViewName() {
return "Vanilla Packs";
}
}

View file

@ -0,0 +1,631 @@
/*
* ATLauncher - https://github.com/ATLauncher/ATLauncher
* Copyright (C) 2013-2022 ATLauncher
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.atlauncher.gui.tabs
import com.atlauncher.App
import com.atlauncher.builders.HTMLBuilder
import com.atlauncher.constants.UIConstants
import com.atlauncher.data.MCVersionRow
import com.atlauncher.data.minecraft.loaders.LoaderType
import com.atlauncher.data.minecraft.loaders.LoaderVersion
import com.atlauncher.evnt.listener.RelocalizationListener
import com.atlauncher.evnt.manager.RelocalizationManager
import com.atlauncher.managers.DialogManager
import com.atlauncher.utils.ComboItem
import com.atlauncher.viewmodel.base.IVanillaPacksViewModel
import com.atlauncher.viewmodel.impl.VanillaPacksViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.mini2Dx.gettext.GetText
import java.awt.*
import java.awt.event.ActionEvent
import java.awt.event.ActionListener
import java.awt.event.KeyAdapter
import java.awt.event.KeyEvent
import javax.swing.*
import javax.swing.event.ListSelectionListener
import javax.swing.table.DefaultTableModel
import kotlin.math.max
import kotlin.math.min
class VanillaPacksTab : JPanel(BorderLayout()), Tab, RelocalizationListener {
private val nameField = JTextField(32)
private val descriptionField = JTextArea(2, 40)
private fun getReleasesText() = GetText.tr("Releases")
private val minecraftVersionReleasesFilterCheckbox = JCheckBox(getReleasesText())
private fun getExperimentsText() = GetText.tr("Experiments")
private val minecraftVersionExperimentsFilterCheckbox = JCheckBox(getExperimentsText())
private fun getSnapshotsText() = GetText.tr("Snapshots")
private val minecraftVersionSnapshotsFilterCheckbox = JCheckBox(getSnapshotsText())
private fun getBetasText() = GetText.tr("Betas")
private val minecraftVersionBetasFilterCheckbox = JCheckBox(getBetasText())
private fun getAlphasText() = GetText.tr("Alphas")
private val minecraftVersionAlphasFilterCheckbox = JCheckBox(getAlphasText())
private var minecraftVersionTable: JTable? = null
private var minecraftVersionTableModel: DefaultTableModel? = null
private val loaderTypeButtonGroup = ButtonGroup()
private fun getNoneText() = GetText.tr("None")
private val loaderTypeNoneRadioButton = JRadioButton(getNoneText())
private val loaderTypeFabricRadioButton = JRadioButton("Fabric")
private val loaderTypeForgeRadioButton = JRadioButton("Forge")
private val loaderTypeLegacyFabricRadioButton = JRadioButton("Legacy Fabric")
private val loaderTypeQuiltRadioButton = JRadioButton("Quilt")
private val loaderVersionsDropDown = JComboBox<ComboItem<LoaderVersion?>>()
private fun getCreateServerText() = GetText.tr("Create Server")
private val createServerButton = JButton(getCreateServerText())
private fun getCreateInstanceText() = GetText.tr("Create Instance")
private val createInstanceButton = JButton(getCreateInstanceText())
private val viewModel: IVanillaPacksViewModel = VanillaPacksViewModel()
private val scope = CoroutineScope(Dispatchers.Main)
init {
name = "vanillaPacksPanel"
setupMainPanel()
setupBottomPanel()
RelocalizationManager.addListener(this)
}
private fun setupMainPanel() {
val mainPanel = JPanel(GridBagLayout())
val gbc = GridBagConstraints()
// Name
gbc.gridx = 0
gbc.gridy = 0
gbc.insets = UIConstants.LABEL_INSETS
gbc.anchor = GridBagConstraints.EAST
val nameLabel = JLabel(GetText.tr("Instance Name") + ":")
mainPanel.add(nameLabel, gbc)
gbc.gridx++
gbc.insets = UIConstants.FIELD_INSETS
gbc.anchor = GridBagConstraints.BASELINE_LEADING
scope.launch {
viewModel.name.collect {
nameField.text = it
}
}
nameField.addKeyListener(object : KeyAdapter() {
override fun keyTyped(e: KeyEvent?) {
viewModel.setName(nameField.text)
}
})
mainPanel.add(nameField, gbc)
// Description
gbc.gridx = 0
gbc.gridy++
gbc.insets = UIConstants.LABEL_INSETS
gbc.anchor = GridBagConstraints.BASELINE_TRAILING
val descriptionLabel = JLabel(GetText.tr("Description") + ":")
mainPanel.add(descriptionLabel, gbc)
gbc.gridx++
gbc.insets = UIConstants.FIELD_INSETS
gbc.anchor = GridBagConstraints.BASELINE_LEADING
val descriptionScrollPane = JScrollPane(
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER
)
descriptionScrollPane.preferredSize = Dimension(450, 80)
descriptionScrollPane.setViewportView(descriptionField)
scope.launch {
viewModel.description.collect {
descriptionField.text = it
}
}
descriptionField.addKeyListener(object : KeyAdapter() {
override fun keyTyped(e: KeyEvent?) {
viewModel.setDescription(descriptionField.text)
}
})
mainPanel.add(descriptionScrollPane, gbc)
// Minecraft Version
gbc.gridx = 0
gbc.gridy += 2
gbc.insets = UIConstants.LABEL_INSETS
gbc.anchor = GridBagConstraints.NORTHEAST
val minecraftVersionPanel = JPanel()
minecraftVersionPanel.layout = BoxLayout(minecraftVersionPanel, BoxLayout.Y_AXIS)
val minecraftVersionLabel = JLabel(GetText.tr("Minecraft Version") + ":")
minecraftVersionPanel.add(minecraftVersionLabel)
minecraftVersionPanel.add(Box.createVerticalStrut(20))
val minecraftVersionFilterPanel = JPanel()
minecraftVersionFilterPanel.layout = BoxLayout(minecraftVersionFilterPanel, BoxLayout.Y_AXIS)
val minecraftVersionFilterLabel = JLabel(GetText.tr("Filter"))
scope.launch {
viewModel.font.collect {
minecraftVersionFilterLabel.font = it
}
}
minecraftVersionFilterPanel.add(minecraftVersionFilterLabel)
// Release checkbox
setupReleaseCheckbox(minecraftVersionFilterPanel)
// Experiments checkbox
setupExperimentsCheckbox(minecraftVersionFilterPanel)
// Snapshots checkbox
setupSnapshotsCheckbox(minecraftVersionFilterPanel)
// Old Betas checkbox
setupOldBetasCheckbox(minecraftVersionFilterPanel)
// Old Alphas checkbox
setupOldAlphasCheckbox(minecraftVersionFilterPanel)
minecraftVersionPanel.add(minecraftVersionFilterPanel)
mainPanel.add(minecraftVersionPanel, gbc)
gbc.gridx++
gbc.insets = UIConstants.FIELD_INSETS
gbc.anchor = GridBagConstraints.BASELINE_LEADING
val minecraftVersionScrollPane = JScrollPane(
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER
)
minecraftVersionScrollPane.preferredSize = Dimension(450, 300)
setupMinecraftVersionsTable()
minecraftVersionScrollPane.setViewportView(minecraftVersionTable)
mainPanel.add(minecraftVersionScrollPane, gbc)
// Loader Type
gbc.gridx = 0
gbc.gridy++
gbc.insets = UIConstants.LABEL_INSETS
gbc.anchor = GridBagConstraints.EAST
val loaderTypeLabel = JLabel(GetText.tr("Loader") + "?")
mainPanel.add(loaderTypeLabel, gbc)
gbc.gridx++
gbc.insets = UIConstants.FIELD_INSETS
gbc.anchor = GridBagConstraints.BASELINE_LEADING
loaderTypeButtonGroup.add(loaderTypeNoneRadioButton)
loaderTypeButtonGroup.add(loaderTypeFabricRadioButton)
loaderTypeButtonGroup.add(loaderTypeForgeRadioButton)
loaderTypeButtonGroup.add(loaderTypeLegacyFabricRadioButton)
loaderTypeButtonGroup.add(loaderTypeQuiltRadioButton)
val loaderTypePanel = JPanel(FlowLayout())
setupLoaderNoneButton(loaderTypePanel)
setupLoaderFabricButton(loaderTypePanel)
setupLoaderLegacyFabricButton(loaderTypePanel)
setupLoaderForgeButton(loaderTypePanel)
setupLoaderQuiltButton(loaderTypePanel)
mainPanel.add(loaderTypePanel, gbc)
// Loader Version
gbc.gridx = 0
gbc.gridy++
gbc.insets = UIConstants.LABEL_INSETS
gbc.anchor = GridBagConstraints.BASELINE_TRAILING
val loaderVersionLabel = JLabel(GetText.tr("Loader Version") + ":")
mainPanel.add(loaderVersionLabel, gbc)
gbc.gridx++
gbc.insets = UIConstants.FIELD_INSETS
gbc.anchor = GridBagConstraints.BASELINE_LEADING
scope.launch {
viewModel.loaderVersionsDropDownEnabled.collect {
loaderVersionsDropDown.isEnabled = it
}
}
scope.launch {
viewModel.loaderVersions.collectLatest { loaderVersions ->
loaderVersionsDropDown.removeAllItems()
if (loaderVersions == null) {
setEmpty()
} else {
var loaderVersionLength = 0
loaderVersions.forEach { version ->
// ensures that font width is taken into account
loaderVersionLength = max(
loaderVersionLength,
getFontMetrics(App.THEME.normalFont)
.stringWidth(version.toString()) + 25
)
loaderVersionsDropDown.addItem(
ComboItem(
version,
version.version
)
)
}
// ensures that the dropdown is at least 200 px wide
loaderVersionLength = max(200, loaderVersionLength)
// ensures that there is a maximum width of 400 px to prevent overflow
loaderVersionLength = min(400, loaderVersionLength)
loaderVersionsDropDown.preferredSize = Dimension(loaderVersionLength, 23)
viewModel.selectedLoaderVersion.collect {
if (it != null)
loaderVersionsDropDown.selectedIndex =
loaderVersions.indexOf(it)
}
}
}
}
scope.launch {
viewModel.loaderLoading.collect {
loaderVersionsDropDown.removeAllItems()
if (it) {
loaderVersionsDropDown.addItem(ComboItem(null, GetText.tr("Getting Loader Versions")))
} else {
setEmpty()
}
}
}
mainPanel.add(loaderVersionsDropDown, gbc)
add(mainPanel, BorderLayout.CENTER)
}
private fun setEmpty() {
loaderVersionsDropDown.addItem(ComboItem(null, GetText.tr("Select Loader First")))
}
private fun setupLoaderQuiltButton(loaderTypePanel: JPanel) {
scope.launch {
viewModel.loaderTypeQuiltSelected.collect {
loaderTypeQuiltRadioButton.isSelected = it
}
}
scope.launch {
viewModel.loaderTypeQuiltEnabled.collect {
loaderTypeQuiltRadioButton.isEnabled = it
}
}
scope.launch {
viewModel.isQuiltVisible.collect {
loaderTypeQuiltRadioButton.isVisible = it
}
}
loaderTypeQuiltRadioButton.addActionListener { e: ActionEvent? ->
viewModel.setLoaderType(
LoaderType.QUILT
)
}
if (viewModel.showQuiltOption) {
loaderTypePanel.add(loaderTypeQuiltRadioButton)
}
}
private fun setupLoaderForgeButton(loaderTypePanel: JPanel) {
scope.launch {
viewModel.loaderTypeForgeSelected.collect {
loaderTypeForgeRadioButton.isSelected = it
}
}
scope.launch {
viewModel.loaderTypeForgeEnabled.collect {
loaderTypeForgeRadioButton.isEnabled = it
}
}
scope.launch {
viewModel.isForgeVisible.collect {
loaderTypeForgeRadioButton.isVisible = it
}
}
loaderTypeForgeRadioButton.addActionListener { e: ActionEvent? ->
viewModel.setLoaderType(
LoaderType.FORGE
)
}
if (viewModel.showForgeOption) {
loaderTypePanel.add(loaderTypeForgeRadioButton)
}
}
private fun setupLoaderLegacyFabricButton(loaderTypePanel: JPanel) {
scope.launch {
viewModel.loaderTypeLegacyFabricSelected.collect {
loaderTypeLegacyFabricRadioButton.isSelected = it
}
}
scope.launch {
viewModel.loaderTypeLegacyFabricEnabled.collect {
loaderTypeLegacyFabricRadioButton.isEnabled = it
}
}
scope.launch {
viewModel.isLegacyFabricVisible.collect {
loaderTypeLegacyFabricRadioButton.isVisible = it
}
}
loaderTypeLegacyFabricRadioButton.addActionListener { e: ActionEvent? ->
viewModel.setLoaderType(
LoaderType.LEGACY_FABRIC
)
}
if (viewModel.showLegacyFabricOption) {
loaderTypePanel.add(loaderTypeLegacyFabricRadioButton)
}
}
private fun setupLoaderFabricButton(loaderTypePanel: JPanel) {
scope.launch {
viewModel.loaderTypeFabricSelected.collect {
loaderTypeFabricRadioButton.isSelected = it
}
}
scope.launch {
viewModel.loaderTypeFabricEnabled.collect {
loaderTypeFabricRadioButton.isEnabled = it
}
}
scope.launch {
viewModel.isFabricVisible.collect {
loaderTypeFabricRadioButton.isVisible = it
}
}
loaderTypeFabricRadioButton.addActionListener { e: ActionEvent? ->
viewModel.setLoaderType(
LoaderType.FABRIC
)
}
if (viewModel.showFabricOption) {
loaderTypePanel.add(loaderTypeFabricRadioButton)
}
}
private fun setupLoaderNoneButton(loaderTypePanel: JPanel) {
scope.launch {
viewModel.loaderTypeNoneSelected.collect {
loaderTypeNoneRadioButton.isSelected = it
}
}
scope.launch {
viewModel.loaderTypeNoneEnabled.collect {
loaderTypeNoneRadioButton.isEnabled = it
}
}
loaderTypeNoneRadioButton.addActionListener { e: ActionEvent? ->
viewModel.setLoaderType(null)
}
loaderTypePanel.add(loaderTypeNoneRadioButton)
}
private fun setupOldAlphasCheckbox(minecraftVersionFilterPanel: JPanel) {
scope.launch {
viewModel.oldAlphaSelected.collect {
minecraftVersionAlphasFilterCheckbox.isSelected = it
}
}
scope.launch {
viewModel.oldAlphaEnabled.collect {
minecraftVersionAlphasFilterCheckbox.isEnabled = it
}
}
minecraftVersionAlphasFilterCheckbox.addActionListener {
viewModel.setOldAlphaSelected(minecraftVersionAlphasFilterCheckbox.isSelected)
}
if (viewModel.showOldAlphaOption) {
minecraftVersionFilterPanel.add(minecraftVersionAlphasFilterCheckbox)
}
}
private fun setupOldBetasCheckbox(minecraftVersionFilterPanel: JPanel) {
scope.launch {
viewModel.oldBetaSelected.collect {
minecraftVersionBetasFilterCheckbox.isSelected = it
}
}
scope.launch {
viewModel.oldBetaEnabled.collect {
minecraftVersionBetasFilterCheckbox.isEnabled = it
}
}
minecraftVersionBetasFilterCheckbox.addActionListener {
viewModel.setOldBetaSelected(minecraftVersionBetasFilterCheckbox.isSelected)
}
if (viewModel.showOldBetaOption) {
minecraftVersionFilterPanel.add(minecraftVersionBetasFilterCheckbox)
}
}
private fun setupSnapshotsCheckbox(minecraftVersionFilterPanel: JPanel) {
scope.launch {
viewModel.snapshotSelected.collect {
minecraftVersionSnapshotsFilterCheckbox.isSelected = it
}
}
scope.launch {
viewModel.snapshotEnabled.collect {
minecraftVersionSnapshotsFilterCheckbox.isEnabled = it
}
}
minecraftVersionSnapshotsFilterCheckbox.addActionListener {
viewModel.setSnapshotSelected(minecraftVersionSnapshotsFilterCheckbox.isSelected)
}
if (viewModel.showSnapshotOption) {
minecraftVersionFilterPanel.add(minecraftVersionSnapshotsFilterCheckbox)
}
}
private fun setupExperimentsCheckbox(minecraftVersionFilterPanel: JPanel) {
scope.launch {
viewModel.experimentSelected.collect {
minecraftVersionExperimentsFilterCheckbox.isSelected = it
}
}
scope.launch {
viewModel.experimentEnabled.collect {
minecraftVersionExperimentsFilterCheckbox.isEnabled = it
}
}
minecraftVersionExperimentsFilterCheckbox.addActionListener {
viewModel.setExperimentSelected(minecraftVersionExperimentsFilterCheckbox.isSelected)
}
if (viewModel.showExperimentOption) {
minecraftVersionFilterPanel.add(minecraftVersionExperimentsFilterCheckbox)
}
}
private fun setupReleaseCheckbox(minecraftVersionFilterPanel: JPanel) {
scope.launch {
viewModel.releaseSelected.collect {
minecraftVersionReleasesFilterCheckbox.isSelected = it
}
}
scope.launch {
viewModel.releaseEnabled.collect {
minecraftVersionReleasesFilterCheckbox.isEnabled = it
}
}
minecraftVersionReleasesFilterCheckbox.isSelected = true
minecraftVersionReleasesFilterCheckbox.addActionListener {
viewModel.setReleaseSelected(minecraftVersionReleasesFilterCheckbox.isSelected)
}
if (viewModel.showReleaseOption) {
minecraftVersionFilterPanel.add(minecraftVersionReleasesFilterCheckbox)
}
}
private fun setupMinecraftVersionsTable() {
minecraftVersionTableModel = object : DefaultTableModel(
arrayOf<Array<String>>(), arrayOf(GetText.tr("Version"), GetText.tr("Released"), GetText.tr("Type"))
) {
override fun isCellEditable(rowIndex: Int, columnIndex: Int): Boolean {
return false
}
}
minecraftVersionTable = JTable(minecraftVersionTableModel)
minecraftVersionTable!!.tableHeader.reorderingAllowed = false
val sm = minecraftVersionTable!!.selectionModel
sm.addListSelectionListener(ListSelectionListener { e ->
if (e.valueIsAdjusting) {
return@ListSelectionListener
}
val lsm = e.source as ListSelectionModel
val minIndex = e.firstIndex
val maxIndex = e.lastIndex
for (i in minIndex..maxIndex) {
if (lsm.isSelectedIndex(i)) {
viewModel.setSelectedMinecraftVersion(
minecraftVersionTableModel!!.getValueAt(
i, 0
) as String
)
}
}
})
scope.launch {
viewModel.minecraftVersions.collectLatest { minecraftVersions ->
// remove all rows
val rowCount = minecraftVersionTableModel?.rowCount ?: 0
if (rowCount > 0) {
for (i in rowCount - 1 downTo 0) {
minecraftVersionTableModel?.removeRow(i)
}
}
minecraftVersions.forEach { row: MCVersionRow ->
minecraftVersionTableModel?.addRow(
arrayOf(
row.id,
row.date,
row.type
)
)
}
// refresh the table
minecraftVersionTable?.revalidate()
}
}
scope.launch {
viewModel.selectedMinecraftVersionIndex.collect {
if (it < (minecraftVersionTable?.rowCount ?: 0)) {
minecraftVersionTable?.setRowSelectionInterval(it, it)
minecraftVersionTable?.revalidate()
}
}
}
val cm = minecraftVersionTable!!.columnModel
cm.getColumn(0).resizable = false
cm.getColumn(1).resizable = false
cm.getColumn(1).maxWidth = 200
cm.getColumn(2).resizable = false
cm.getColumn(2).maxWidth = 200
minecraftVersionTable!!.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
minecraftVersionTable!!.showVerticalLines = false
}
private fun setupBottomPanel() {
val bottomPanel = JPanel(FlowLayout())
bottomPanel.add(createServerButton)
createServerButton.addActionListener(ActionListener { // user has no instances, they may not be aware this is not how to play
if (viewModel.warnUserAboutServer) {
val ret = DialogManager.yesNoDialog().setTitle(GetText.tr("Are you sure you want to create a server?"))
.setContent(
HTMLBuilder().center().text(
GetText.tr(
"Creating a server won't allow you play Minecraft, it's for letting others play together.<br/><br/>If you just want to play Minecraft, you don't want to create a server, and instead will want to create an instance.<br/><br/>Are you sure you want to create a server?"
)
).build()
).setType(DialogManager.QUESTION).show()
if (ret != 0) {
return@ActionListener
}
}
viewModel.createServer()
})
bottomPanel.add(createInstanceButton)
createInstanceButton.addActionListener { viewModel.createInstance() }
add(bottomPanel, BorderLayout.SOUTH)
}
override fun getTitle(): String {
return GetText.tr("Vanilla Packs")
}
override fun getAnalyticsScreenViewName(): String {
return "Vanilla Packs"
}
override fun onRelocalization() {
minecraftVersionReleasesFilterCheckbox.text = getReleasesText()
minecraftVersionExperimentsFilterCheckbox.text = getExperimentsText()
minecraftVersionSnapshotsFilterCheckbox.text = getSnapshotsText()
minecraftVersionBetasFilterCheckbox.text = getBetasText()
minecraftVersionAlphasFilterCheckbox.text = getAlphasText()
loaderTypeNoneRadioButton.text = getNoneText()
createServerButton.text = getCreateServerText()
createInstanceButton.text = getCreateInstanceText()
}
}

View file

@ -0,0 +1,336 @@
/*
* ATLauncher - https://github.com/ATLauncher/ATLauncher
* Copyright (C) 2013-2022 ATLauncher
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.atlauncher.viewmodel.base
import com.atlauncher.data.MCVersionRow
import com.atlauncher.data.minecraft.loaders.LoaderType
import com.atlauncher.data.minecraft.loaders.LoaderVersion
import kotlinx.coroutines.flow.Flow
import java.awt.Font
/**
* 25 / 06 / 2022
*/
interface IVanillaPacksViewModel {
// UI settings
/**
* Controls what font to use
*/
val font: Flow<Font>
// Pack Options
/**
* Name of the new instance
*/
val name: Flow<String?>
/**
* Set the name of the new instance
*/
fun setName(name: String)
/**
* Description of the new instance
*/
val description: Flow<String?>
/**
* Set the new description of the new instance
*/
fun setDescription(description: String)
// Release Options
/**
* If the release checkbox should be visible
*/
val showReleaseOption: Boolean
/**
* If the experiment checkbox should be visible
*/
val showExperimentOption: Boolean
/**
* If the snapshot checkbox should be visible
*/
val showSnapshotOption: Boolean
/**
* If the old alpha option should be visible
*/
val showOldAlphaOption: Boolean
/**
* If the old beta option should be visible
*/
val showOldBetaOption: Boolean
/**
* Is the release checkbox selected
*/
val releaseSelected: Flow<Boolean>
/**
* Is the release checkbox enabled
*/
val releaseEnabled: Flow<Boolean>
/**
* Is the experiment checkbox selected
*/
val experimentSelected: Flow<Boolean>
/**
* Is the experiment checkbox enabled
*/
val experimentEnabled: Flow<Boolean>
/**
* Is the snapshot checkbox selected
*/
val snapshotSelected: Flow<Boolean>
/**
* Is the snapshot checkbox enabled
*/
val snapshotEnabled: Flow<Boolean>
/**
* Is the alpha checkbox selected
*/
val oldAlphaSelected: Flow<Boolean>
/**
* Is the alpha checkbox enabled
*/
val oldAlphaEnabled: Flow<Boolean>
/**
* Is the beta checkbox selected
*/
val oldBetaSelected: Flow<Boolean>
/**
* Is the beta checkbox enabled
*/
val oldBetaEnabled: Flow<Boolean>
/**
* Set [releaseSelected] or not
*
* Should reload [minecraftVersions]
*/
fun setReleaseSelected(b: Boolean)
/**
* Set [experimentSelected] or not
*
* Should reload [minecraftVersions]
*/
fun setExperimentSelected(b: Boolean)
/**
* Set [snapshotSelected] or not
*
* Should reload [minecraftVersions]
*/
fun setSnapshotSelected(b: Boolean)
/**
* Set [oldAlphaSelected] or not
*
* Should reload [minecraftVersions]
*/
fun setOldAlphaSelected(b: Boolean)
/**
* Set [oldBetaSelected] or not
*
* Should reload [minecraftVersions]
*/
fun setOldBetaSelected(b: Boolean)
/**
* Minecraft versions to display in the version table
*/
val minecraftVersions: Flow<Array<MCVersionRow>>
/**
* Which index is currently selected
*/
val selectedMinecraftVersionIndex: Flow<Int>
/**
* Set the selected minecraft version
*/
fun setSelectedMinecraftVersion(valueAt: String?)
// Mod loader option
/**
* Is fabric an option currently
*/
val isFabricVisible: Flow<Boolean>
/**
* Is legacy fabric an option currently
*/
val isLegacyFabricVisible: Flow<Boolean>
/**
* Is forge an option currently
*/
val isForgeVisible: Flow<Boolean>
/**
* Is quilt an option currently
*/
val isQuiltVisible: Flow<Boolean>
/**
* If fabric is enabled in config or not
*/
val showFabricOption: Boolean
/**
* If forge is enabled in config or not
*/
val showForgeOption: Boolean
/**
* If legacy forge should be enabled or not
*/
val showLegacyFabricOption: Boolean
/**
* If quilt is enabled in config or not
*/
val showQuiltOption: Boolean
/**
* Has none been selected
*/
val loaderTypeNoneSelected: Flow<Boolean>
/**
* Is the none button enabled or not
*/
val loaderTypeNoneEnabled: Flow<Boolean>
/**
* Has fabric been selected
*/
val loaderTypeFabricSelected: Flow<Boolean>
/**
* Is the fabric button enabled or not
*/
val loaderTypeFabricEnabled: Flow<Boolean>
/**
* Has forge been selected
*/
val loaderTypeForgeSelected: Flow<Boolean>
/**
* Is legacy fabric selected
*/
val loaderTypeLegacyFabricSelected: Flow<Boolean>
/**
* Is the forge button enabled or not
*/
val loaderTypeForgeEnabled: Flow<Boolean>
/**
* Is legacy fabric enabled
*/
val loaderTypeLegacyFabricEnabled: Flow<Boolean>
/**
* Has quilt been selected
*/
val loaderTypeQuiltSelected: Flow<Boolean>
/**
* Is the quilt button enabled or not
*/
val loaderTypeQuiltEnabled: Flow<Boolean>
/**
* Set the loader type
*
* Will cause a reload of [loaderVersions]
*/
fun setLoaderType(loader: LoaderType?)
/**
* Set the selected loader version
*/
fun setLoaderVersion(loaderVersion: String)
/**
* Is the loader versions drop down enabled
*/
val loaderVersionsDropDownEnabled: Flow<Boolean>
/**
* Loader versions that the user can select for the loaderType
*/
val loaderVersions: Flow<Array<LoaderVersion>?>
/**
* The selected mod loader version
*/
val selectedLoaderVersion: Flow<LoaderVersion?>
/**
* Is the loader loading.
*
* This value is used to show a little loading icon
*/
val loaderLoading: Flow<Boolean>
/**
* Is the create server button enabled or not
*/
val createServerEnabled: Flow<Boolean>
/**
* Is the create instance button enabled or not
*/
val createInstanceEnabled: Flow<Boolean>
/**
* Create a server with the provided information
*/
fun createServer()
/**
* Create an instance with the provided information
*/
fun createInstance()
/**
* Warn the user about creating a server =/= playing the game themselves
*/
val warnUserAboutServer: Boolean
}

View file

@ -0,0 +1,676 @@
/*
* ATLauncher - https://github.com/ATLauncher/ATLauncher
* Copyright (C) 2013-2022 ATLauncher
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.atlauncher.viewmodel.impl
import com.apollographql.apollo.api.cache.http.HttpCachePolicy
import com.apollographql.apollo.coroutines.await
import com.apollographql.apollo.exception.ApolloException
import com.atlauncher.App
import com.atlauncher.data.installables.Installable
import com.atlauncher.data.installables.VanillaInstallable
import com.atlauncher.data.minecraft.VersionManifestVersion
import com.atlauncher.data.minecraft.VersionManifestVersionType
import com.atlauncher.data.minecraft.loaders.LoaderType
import com.atlauncher.data.minecraft.loaders.LoaderVersion
import com.atlauncher.data.minecraft.loaders.fabric.FabricLoader
import com.atlauncher.data.minecraft.loaders.forge.ForgeLoader
import com.atlauncher.data.minecraft.loaders.legacyfabric.LegacyFabricLoader
import com.atlauncher.data.minecraft.loaders.quilt.QuiltLoader
import com.atlauncher.evnt.listener.SettingsListener
import com.atlauncher.evnt.manager.SettingsManager
import com.atlauncher.exceptions.InvalidMinecraftVersion
import com.atlauncher.graphql.GetLoaderVersionsForMinecraftVersionQuery
import com.atlauncher.managers.ConfigManager
import com.atlauncher.managers.InstanceManager
import com.atlauncher.managers.LogManager
import com.atlauncher.managers.MinecraftManager
import com.atlauncher.network.GraphqlClient
import com.atlauncher.utils.Pair
import com.atlauncher.utils.Utils
import com.atlauncher.viewmodel.base.IVanillaPacksViewModel
import com.atlauncher.data.MCVersionRow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import org.joda.time.format.DateTimeFormat
import org.joda.time.format.ISODateTimeFormat
import org.mini2Dx.gettext.GetText
import java.awt.Font
import java.util.concurrent.TimeUnit
import java.util.stream.Collectors
/**
* 25 / 06 / 2022
*/
class VanillaPacksViewModel : IVanillaPacksViewModel, SettingsListener {
private val scope = CoroutineScope(Dispatchers.IO)
/**
* Filters applied to the version table
*/
private val minecraftVersionTypeFiltersFlow = MutableStateFlow(mapOf(VersionManifestVersionType.RELEASE to true))
/**
* Name is dirty, as the user has inputted something
*/
private var nameDirty = false
/**
* Name to display
*/
override val name = MutableStateFlow<String?>(null)
/**
* Description is dirty, as the user has inputted something
*/
private var descriptionDirty = false
/**
* Description to display
*/
override val description = MutableStateFlow<String?>(null)
private val selectedMinecraftVersionFlow = MutableStateFlow<String?>(null)
override val selectedMinecraftVersionIndex: Flow<Int> by lazy {
minecraftVersions.combine(selectedMinecraftVersionFlow) { versions, version ->
val index = versions.indexOfFirst { it.id == version }
if (index == -1) return@combine 0
return@combine index
}
}
private val selectedLoaderType = MutableStateFlow<LoaderType?>(null)
override val selectedLoaderVersion = MutableStateFlow<LoaderVersion?>(null)
override val loaderLoading = MutableStateFlow(false)
override val font = MutableStateFlow<Font>(App.THEME.boldFont)
// Consumers
/**
* Represents the version list
*/
override val minecraftVersions: Flow<Array<MCVersionRow>> by lazy {
minecraftVersionTypeFiltersFlow.map { versionFilter: Map<VersionManifestVersionType, Boolean> ->
val filtered = versionFilter.filter { it.value }.map { it.key }.toList()
val fmt = DateTimeFormat.forPattern(App.settings.dateFormat)
MinecraftManager.getFilteredMinecraftVersions(filtered).map { it: VersionManifestVersion ->
MCVersionRow(
it.id, fmt.print(ISODateTimeFormat.dateTimeParser().parseDateTime(it.releaseTime)), it.id
)
}.toTypedArray()
}
}
override val loaderVersionsDropDownEnabled = MutableStateFlow(false)
/**
* Presents the loader versions for the user to select
*/
override val loaderVersions = MutableStateFlow<Array<LoaderVersion>?>(null)
override val createServerEnabled = MutableStateFlow(false)
override val createInstanceEnabled = MutableStateFlow(false)
override val isFabricVisible by lazy {
selectedMinecraftVersionFlow.map { version ->
!fabricDisabledMCVersions.contains(version)
}
}
override val isLegacyFabricVisible: Flow<Boolean> by lazy {
selectedMinecraftVersionFlow.map { version ->
!legacyFabricDisabledMCVersions.contains(version)
}
}
override val isForgeVisible by lazy {
selectedMinecraftVersionFlow.map { version ->
!forgeDisabledMCVersions.contains(version)
}
}
override val isQuiltVisible by lazy {
selectedMinecraftVersionFlow.map { version ->
!quiltDisabledMCVersions.contains(version)
}
}
override val loaderTypeFabricSelected: Flow<Boolean> = selectedLoaderType.map {
it == LoaderType.FABRIC
}
override val loaderTypeFabricEnabled = MutableStateFlow(true)
override val loaderTypeForgeSelected: Flow<Boolean> = selectedLoaderType.map {
it == LoaderType.FORGE
}
override val loaderTypeLegacyFabricSelected: Flow<Boolean> =
selectedLoaderType.map {
it == LoaderType.LEGACY_FABRIC
}
override val loaderTypeForgeEnabled = MutableStateFlow(true)
override val loaderTypeLegacyFabricEnabled = MutableStateFlow(true)
override val loaderTypeNoneSelected: Flow<Boolean> = selectedLoaderType.map {
it == null
}
override val loaderTypeNoneEnabled = MutableStateFlow(true)
override val loaderTypeQuiltSelected: Flow<Boolean> = selectedLoaderType.map {
it == LoaderType.QUILT
}
override val loaderTypeQuiltEnabled = MutableStateFlow(true)
/**
* Just a small copy function, to copy the values of a map.
*
* This is useful for flows, as a map is considered a persistent structure.
* Thus, modifications to the map doesn't "change" the map.
*/
private fun <K, V> Map<K, V>.copy(): Map<K, V> {
val map = HashMap<K, V>()
this.forEach { (k, v) ->
map[k] = v
}
return map
}
private fun install(isServer: Boolean) {
val installable: Installable
try {
val selectedLoaderVersion = selectedLoaderVersion.value
val selectedMinecraftVersion = selectedMinecraftVersionFlow.value ?: return
val description = description.value
val name = name.value
installable = VanillaInstallable(
MinecraftManager.getMinecraftVersion(selectedMinecraftVersion),
selectedLoaderVersion,
description
)
installable.instanceName = name
installable.isReinstall = false
installable.isServer = isServer
val success = installable.startInstall()
if (success) {
// - Reset the view, currently disabled
//nameFieldDirty = false
//descriptionFieldDirty = false
//loaderTypeNoneRadioButton.isSelected = true
//selectedLoaderTypeChanged(null)
//minecraftVersionTable!!.setRowSelectionInterval(0, 0)
}
} catch (e: InvalidMinecraftVersion) {
LogManager.logStackTrace(e)
}
}
private fun isNameDirty() = !(name.value == String.format(
"Minecraft %s", selectedMinecraftVersionFlow.value
) || selectedLoaderType.value != null && name.value == String.format(
"Minecraft %s with %s", selectedMinecraftVersionFlow.value, selectedLoaderType.value
))
override fun setName(name: String) {
LogManager.debug("setName $name")
this.name.value = (name)
nameDirty = isNameDirty()
}
private fun isDescriptionDirty() = !(description.value == String.format(
"Minecraft %s", selectedMinecraftVersionFlow.value
) || selectedLoaderType.value != null && description.value == String.format(
"Minecraft %s with %s", selectedMinecraftVersionFlow.value, selectedLoaderType.value
))
override fun setDescription(description: String) {
descriptionDirty = isDescriptionDirty()
this.description.value = (description)
}
override val showReleaseOption: Boolean by lazy { ConfigManager.getConfigItem("minecraft.release.enabled", true) }
override val showExperimentOption: Boolean by lazy {
ConfigManager.getConfigItem(
"minecraft.experiment.enabled",
true
)
}
override val showSnapshotOption: Boolean by lazy { ConfigManager.getConfigItem("minecraft.snapshot.enabled", true) }
override val showOldAlphaOption: Boolean by lazy {
ConfigManager.getConfigItem(
"minecraft.old_alpha.enabled",
true
)
}
override val showOldBetaOption: Boolean by lazy { ConfigManager.getConfigItem("minecraft.old_beta.enabled", true) }
override fun setReleaseSelected(b: Boolean) {
val map = HashMap(minecraftVersionTypeFiltersFlow.value)
map[VersionManifestVersionType.RELEASE] = b
minecraftVersionTypeFiltersFlow.value = map.copy()
}
override val releaseSelected: Flow<Boolean> = minecraftVersionTypeFiltersFlow.map {
it[VersionManifestVersionType.RELEASE] ?: false
}
override val releaseEnabled: Flow<Boolean> = releaseSelected.combine(minecraftVersionTypeFiltersFlow) { a, b ->
!(a && (b.count { it.value } == 1))
}
override fun setExperimentSelected(b: Boolean) {
val map = HashMap(minecraftVersionTypeFiltersFlow.value)
map[VersionManifestVersionType.EXPERIMENT] = b
minecraftVersionTypeFiltersFlow.value = map.copy()
}
override val experimentSelected: Flow<Boolean> = minecraftVersionTypeFiltersFlow.map {
it[VersionManifestVersionType.EXPERIMENT] ?: false
}
override val experimentEnabled: Flow<Boolean> =
experimentSelected.combine(minecraftVersionTypeFiltersFlow) { a, b ->
!(a && (b.count { it.value } == 1))
}
override fun setSnapshotSelected(b: Boolean) {
val map = HashMap(minecraftVersionTypeFiltersFlow.value)
map[VersionManifestVersionType.SNAPSHOT] = b
minecraftVersionTypeFiltersFlow.value = map.copy()
}
override val snapshotSelected: Flow<Boolean> = minecraftVersionTypeFiltersFlow.map {
it[VersionManifestVersionType.SNAPSHOT] ?: false
}
override val snapshotEnabled: Flow<Boolean> = snapshotSelected.combine(minecraftVersionTypeFiltersFlow) { a, b ->
!(a && (b.count { it.value } == 1))
}
override fun setOldAlphaSelected(b: Boolean) {
val map = HashMap(minecraftVersionTypeFiltersFlow.value)
map[VersionManifestVersionType.OLD_ALPHA] = b
minecraftVersionTypeFiltersFlow.value = map.copy()
}
override val oldAlphaSelected: Flow<Boolean> = minecraftVersionTypeFiltersFlow.map {
it[VersionManifestVersionType.OLD_ALPHA] ?: false
}
override val oldAlphaEnabled: Flow<Boolean> = oldAlphaSelected.combine(minecraftVersionTypeFiltersFlow) { a, b ->
!(a && (b.count { it.value } == 1))
}
override fun setOldBetaSelected(b: Boolean) {
val map = HashMap(minecraftVersionTypeFiltersFlow.value)
map[VersionManifestVersionType.OLD_BETA] = b
minecraftVersionTypeFiltersFlow.value = map.copy()
}
override val oldBetaSelected: Flow<Boolean> = minecraftVersionTypeFiltersFlow.map {
it[VersionManifestVersionType.OLD_BETA] ?: false
}
override val oldBetaEnabled: Flow<Boolean> = oldBetaSelected.combine(minecraftVersionTypeFiltersFlow) { a, b ->
!(a && (b.count { it.value } == 1))
}
override fun setSelectedMinecraftVersion(newVersion: String?) {
selectedMinecraftVersionFlow.value = newVersion
}
override val showFabricOption: Boolean by lazy { ConfigManager.getConfigItem("loaders.fabric.enabled", true) }
override val showForgeOption: Boolean by lazy { ConfigManager.getConfigItem("loaders.forge.enabled", true) }
override val showLegacyFabricOption: Boolean by lazy {
ConfigManager.getConfigItem(
"loaders.legacyfabric.enabled",
true
)
}
override val showQuiltOption: Boolean by lazy { ConfigManager.getConfigItem("loaders.quilt.enabled", false) }
override fun setLoaderType(loader: LoaderType?) {
selectedLoaderType.value = loader
}
override fun setLoaderVersion(loaderVersion: String) {
scope.launch {
loaderVersions.first().let { versions ->
if (versions != null) {
for (version in versions) {
if (version.version == loaderVersion) {
selectedLoaderVersion.value = version
break
}
}
}
}
}
}
private val fabricDisabledMCVersions: List<String> by lazy {
ConfigManager.getConfigItem(
"loaders.fabric.disabledMinecraftVersions", emptyList()
)
}
private val legacyFabricDisabledMCVersions: List<String> by lazy {
ConfigManager.getConfigItem(
"loaders.fabric.disabledMinecraftVersions", emptyList()
)
}
private val forgeDisabledMCVersions: List<String> by lazy {
ConfigManager.getConfigItem(
"loaders.forge.disabledMinecraftVersions", emptyList()
)
}
private val quiltDisabledMCVersions: List<String> by lazy {
ConfigManager.getConfigItem(
"loaders.quilt.disabledMinecraftVersions", emptyList()
)
}
override fun createServer() {
install(true)
}
override fun createInstance() {
install(false)
}
override fun onSettingsSaved() {
font.value = (App.THEME.boldFont)
}
override val warnUserAboutServer: Boolean
get() {
return InstanceManager.getInstances().size == 0
}
init {
SettingsManager.addListener(this)
scope.launch {
selectedMinecraftVersionFlow.collect { selectedVersion ->
if (selectedVersion != null) {
try {
val version = MinecraftManager.getMinecraftVersion(selectedVersion)
createServerEnabled.value = version.hasServer()
} catch (ignored: InvalidMinecraftVersion) {
createServerEnabled.value = false
}
} else {
createServerEnabled.value = false
}
val defaultNameFieldValue = String.format(
"Minecraft %s", selectedVersion
)
if (name.value == null || name.value!!.isEmpty() || !nameDirty) {
nameDirty = false
name.value = (defaultNameFieldValue)
}
if (description.value == null || description.value!!.isEmpty() || !descriptionDirty) {
descriptionDirty = false
description.value = (defaultNameFieldValue)
}
}
}
scope.launch {
selectedLoaderType.combine(selectedMinecraftVersionFlow) { a, b ->
a to b
}.collect { (loaderType, selectedMinecraftVersion) ->
if (selectedMinecraftVersion == null) return@collect
loaderVersionsDropDownEnabled.value = false
if (loaderType == null) {
// update the name and description fields if they're not dirty
val defaultNameFieldValue = String.format("Minecraft %s", selectedMinecraftVersion)
if (!nameDirty) {
name.value = defaultNameFieldValue
}
if (!descriptionDirty) {
description.value = defaultNameFieldValue
}
loaderVersions.value = arrayOf(LoaderVersion(GetText.tr("Select Loader First")))
return@collect
}
loaderLoading.value = true
setLoaderGroupEnabled(false)
// Legacy Forge doesn't support servers easily
val enableCreateServers = (loaderType !== LoaderType.FORGE || !Utils.matchVersion(
selectedMinecraftVersion, "1.5", true, true
))
val loaders = if (ConfigManager.getConfigItem("useGraphql.vanillaLoaderVersions", false) == true) {
apolloLoad(loaderType, selectedMinecraftVersion, enableCreateServers)
} else {
legacyLoad(loaderType, selectedMinecraftVersion, enableCreateServers)
}
loaderVersions.value = loaders
if (loaders.isNotEmpty()) {
selectedLoaderVersion.value =
when (loaderType) {
LoaderType.FORGE -> {
loaders.firstOrNull { it.recommended } ?: loaders.first()
}
else -> {
loaders.first()
}
}
}
setLoaderGroupEnabled(true, enableCreateServers)
updateNameAndDescription(selectedMinecraftVersion, loaderType)
}
}
}
suspend fun apolloLoad(
selectedLoader: LoaderType, selectedMinecraftVersion: String?, enableCreateServers: Boolean
): Array<LoaderVersion> {
try {
val response = GraphqlClient.apolloClient.query(
GetLoaderVersionsForMinecraftVersionQuery(
selectedMinecraftVersion!!
)
).toBuilder().httpCachePolicy(
HttpCachePolicy.Policy(
HttpCachePolicy.FetchStrategy.CACHE_FIRST, 5, TimeUnit.MINUTES, false
)
).build().await()
val loaderVersionsList: MutableList<LoaderVersion> = ArrayList()
when (selectedLoader) {
LoaderType.FABRIC -> {
loaderVersionsList.addAll(response.data!!.loaderVersions().fabric().stream()
.filter { fv: GetLoaderVersionsForMinecraftVersionQuery.Fabric ->
!disabledFabricVersions.contains(fv.version())
}.map { version: GetLoaderVersionsForMinecraftVersionQuery.Fabric ->
LoaderVersion(version.version(), false, "Fabric")
}.collect(Collectors.toList())
)
}
LoaderType.FORGE -> {
loaderVersionsList.addAll(response.data!!.loaderVersions().forge().stream()
.filter { fv: GetLoaderVersionsForMinecraftVersionQuery.Forge ->
!disabledForgeVersions.contains(fv.version())
}.map { version: GetLoaderVersionsForMinecraftVersionQuery.Forge ->
val lv = LoaderVersion(
version.version(), version.rawVersion(), version.recommended(), "Forge"
)
if (version.installerSha1Hash() != null && version.installerSize() != null) {
lv.downloadables["installer"] = Pair(
version.installerSha1Hash(), version.installerSize()!!.toLong()
)
}
if (version.universalSha1Hash() != null && version.universalSize() != null) {
lv.downloadables["universal"] = Pair(
version.universalSha1Hash(), version.universalSize()!!.toLong()
)
}
if (version.clientSha1Hash() != null && version.clientSize() != null) {
lv.downloadables["client"] = Pair(
version.clientSha1Hash(), version.clientSize()!!.toLong()
)
}
if (version.serverSha1Hash() != null && version.serverSize() != null) {
lv.downloadables["server"] = Pair(
version.serverSha1Hash(), version.serverSize()!!.toLong()
)
}
lv
}.collect(Collectors.toList())
)
}
LoaderType.QUILT -> {
loaderVersionsList.addAll(response.data!!.loaderVersions().quilt().stream()
.filter { fv: GetLoaderVersionsForMinecraftVersionQuery.Quilt ->
!disabledQuiltVersions.contains(fv.version())
}.map { version: GetLoaderVersionsForMinecraftVersionQuery.Quilt ->
LoaderVersion(version.version(), false, "Quilt")
}.collect(Collectors.toList())
)
}
LoaderType.LEGACY_FABRIC -> {
loaderVersionsList.addAll(response.data!!.loaderVersions().legacyfabric()
.stream()
.filter { fv -> !disabledLegacyFabricVersions.contains(fv.version()) }
.map { version ->
LoaderVersion(
version.version(),
false,
"LegacyFabric"
)
}
.collect(Collectors.toList())
)
}
}
if (loaderVersionsList.size == 0) {
setLoaderGroupEnabled(true, enableCreateServers)
return arrayOf(noLoaderVersions)
}
return loaderVersionsList.toTypedArray()
} catch (e: ApolloException) {
LogManager.logStackTrace("Error fetching loading versions", e)
setLoaderGroupEnabled(true, enableCreateServers)
return arrayOf(errorLoadingVersions)
}
}
private fun setLoaderGroupEnabled(enabled: Boolean, enableCreateServers: Boolean = enabled) {
loaderTypeNoneEnabled.value = enabled
loaderTypeFabricEnabled.value = enabled
loaderTypeForgeEnabled.value = enabled
loaderTypeLegacyFabricEnabled.value = enabled
loaderTypeQuiltEnabled.value = enabled
createServerEnabled.value = enableCreateServers
createInstanceEnabled.value = enabled
loaderVersionsDropDownEnabled.value = enabled
}
private val disabledQuiltVersions: List<String> by lazy {
ConfigManager.getConfigItem(
"loaders.quilt.disabledVersions", emptyList()
)
}
private val disabledFabricVersions: List<String> by lazy {
ConfigManager.getConfigItem(
"loaders.fabric.disabledVersions", emptyList()
)
}
private val disabledLegacyFabricVersions: List<String> by lazy {
ConfigManager.getConfigItem(
"loaders.legacyfabric.disabledVersions", emptyList()
)
}
private val disabledForgeVersions: List<String> by lazy {
ConfigManager.getConfigItem(
"loaders.forge.disabledVersions", emptyList()
)
}
/**
* Use legacy loading mechanic
*/
fun legacyLoad(
selectedLoader: LoaderType, selectedMinecraftVersion: String, enableCreateServers: Boolean
): Array<LoaderVersion> {
val loaderVersionsList: MutableList<LoaderVersion> = ArrayList()
loaderVersionsList.addAll(
when (selectedLoader) {
LoaderType.FABRIC -> FabricLoader.getChoosableVersions(selectedMinecraftVersion)
LoaderType.FORGE -> ForgeLoader.getChoosableVersions(selectedMinecraftVersion)
LoaderType.QUILT -> QuiltLoader.getChoosableVersions(selectedMinecraftVersion)
LoaderType.LEGACY_FABRIC -> LegacyFabricLoader.getChoosableVersions(selectedMinecraftVersion)
}
)
if (loaderVersionsList.size == 0) {
setLoaderGroupEnabled(true, enableCreateServers)
return arrayOf(noLoaderVersions)
}
return loaderVersionsList.toTypedArray()
}
/**
* Update the name and description fields if they're not dirty with loader type information
*/
private fun updateNameAndDescription(
selectedMinecraftVersion: String, selectedLoader: LoaderType
) {
val defaultNameFieldValue = String.format(
"Minecraft %s with %s", selectedMinecraftVersion, selectedLoader.toString()
)
if (!nameDirty) {
name.value = defaultNameFieldValue
}
if (!descriptionDirty) {
description.value = defaultNameFieldValue
}
}
companion object {
private val noLoaderVersions = LoaderVersion(GetText.tr("No Versions Found"))
private val errorLoadingVersions = LoaderVersion(GetText.tr("Error Getting Versions"))
}
}