Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -175,22 +175,57 @@ private void displayFilterDialog() {
TextView mSelectedVersion = dialog.findViewById(R.id.search_mod_selected_mc_version_textview);
Button mSelectVersionButton = dialog.findViewById(R.id.search_mod_mc_version_button);
Button mApplyButton = dialog.findViewById(R.id.search_mod_apply_filters);
android.widget.Spinner mLoaderSpinner = dialog.findViewById(R.id.search_mod_loader_spinner);

assert mSelectedVersion != null;
assert mSelectVersionButton != null;
assert mApplyButton != null;

mSelectVersionButton.setOnClickListener(v ->
VersionSelectorDialog.open(v.getContext(), true,
(id, snapshot) -> mSelectedVersion.setText(id)));
// Set up loader spinner
if (mLoaderSpinner != null) {
String[] loaderLabels = {"Any loader", "Fabric", "Forge", "Quilt", "NeoForge"};
final String[] loaderValues = {"", "fabric", "forge", "quilt", "neoforge"};
android.widget.ArrayAdapter<String> loaderAdapter = new android.widget.ArrayAdapter<>(
requireContext(), android.R.layout.simple_spinner_item, loaderLabels);
loaderAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mLoaderSpinner.setAdapter(loaderAdapter);

// Restore current selection
String currentLoader = mSearchFilters.modLoader != null ? mSearchFilters.modLoader : "";
for (int i = 0; i < loaderValues.length; i++) {
if (loaderValues[i].equals(currentLoader)) {
mLoaderSpinner.setSelection(i);
break;
}
}

mSelectedVersion.setText(mSearchFilters.mcVersion);
mSelectVersionButton.setOnClickListener(v ->
VersionSelectorDialog.open(v.getContext(), true,
(id, snapshot) -> mSelectedVersion.setText(id)));

mApplyButton.setOnClickListener(v -> {
mSearchFilters.mcVersion = mSelectedVersion.getText().toString();
searchMods(mSearchEditText.getText().toString());
dialogInterface.dismiss();
});
mSelectedVersion.setText(mSearchFilters.mcVersion);

mApplyButton.setOnClickListener(v -> {
mSearchFilters.mcVersion = mSelectedVersion.getText().toString();
int pos = mLoaderSpinner.getSelectedItemPosition();
mSearchFilters.modLoader = loaderValues[pos];
searchMods(mSearchEditText.getText().toString());
dialogInterface.dismiss();
});
} else {
// Fallback if spinner view not found
mSelectVersionButton.setOnClickListener(v ->
VersionSelectorDialog.open(v.getContext(), true,
(id, snapshot) -> mSelectedVersion.setText(id)));

mSelectedVersion.setText(mSearchFilters.mcVersion);

mApplyButton.setOnClickListener(v -> {
mSearchFilters.mcVersion = mSelectedVersion.getText().toString();
searchMods(mSearchEditText.getText().toString());
dialogInterface.dismiss();
});
}
});

dialog.show();
Expand Down Expand Up @@ -219,7 +254,9 @@ public ModDetail getModDetails(ModItem item) {
if (item.apiSource == net.kdt.pojavlaunch.modloaders.modpacks.models.Constants.SOURCE_MODRINTH) {
String filterVer = (mFilters.mcVersion != null && !mFilters.mcVersion.isEmpty())
? mFilters.mcVersion : null;
return mModrinthApi.getModDetails(item, filterVer);
String filterLoader = (mFilters.modLoader != null && !mFilters.modLoader.isEmpty())
? mFilters.modLoader : null;
return mModrinthApi.getModDetails(item, filterVer, filterLoader);
}
return super.getModDetails(item);
}
Expand All @@ -238,8 +275,9 @@ public void handleInstallation(Context context, ModDetail modDetail, int selecte
&& (modDetail.isRestricted || url == null || url.isEmpty());

if (isCfRestricted) {
// Show dialog directing user to download from CurseForge website
String cfUrl = "https://www.curseforge.com/minecraft/mc-mods/" + modDetail.id;
String cfUrl = (modDetail.websiteUrl != null && !modDetail.websiteUrl.isEmpty())
? modDetail.websiteUrl
: "https://www.curseforge.com/minecraft/mc-mods/" + modDetail.id;
Context dialogCtx = mActivityContext != null ? mActivityContext : context;
mMainHandler.post(() ->
new AlertDialog.Builder(dialogCtx)
Expand Down Expand Up @@ -358,10 +396,12 @@ private void downloadDependency(String projectId, File modsDir) {
try {
String filterVer = (mFilters.mcVersion != null && !mFilters.mcVersion.isEmpty())
? mFilters.mcVersion : "";
String filterLoader = (mFilters.modLoader != null && !mFilters.modLoader.isEmpty())
? mFilters.modLoader : null;
ModItem depItem = new ModItem(
net.kdt.pojavlaunch.modloaders.modpacks.models.Constants.SOURCE_MODRINTH,
false, projectId, projectId, "", "");
ModDetail depDetail = mModrinthApi.getModDetails(depItem, filterVer.isEmpty() ? null : filterVer);
ModDetail depDetail = mModrinthApi.getModDetails(depItem, filterVer.isEmpty() ? null : filterVer, filterLoader);
if (depDetail == null || depDetail.versionUrls == null || depDetail.versionUrls.length == 0) return;

String depUrl = depDetail.versionUrls[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView;

import androidx.annotation.NonNull;
Expand All @@ -23,6 +25,10 @@
import net.kdt.pojavlaunch.modloaders.modpacks.ModItemAdapter;
import net.kdt.pojavlaunch.modloaders.modpacks.api.CommonApi;
import net.kdt.pojavlaunch.modloaders.modpacks.api.ModpackApi;
import net.kdt.pojavlaunch.modloaders.modpacks.api.ModrinthApi;
import net.kdt.pojavlaunch.modloaders.modpacks.models.Constants;
import net.kdt.pojavlaunch.modloaders.modpacks.models.ModDetail;
import net.kdt.pojavlaunch.modloaders.modpacks.models.ModItem;
import net.kdt.pojavlaunch.modloaders.modpacks.models.SearchFilters;
import net.kdt.pojavlaunch.profiles.VersionSelectorDialog;
import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper;
Expand Down Expand Up @@ -61,7 +67,7 @@ public SearchModFragment(){
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
modpackApi = new CommonApi(context.getString(R.string.curseforge_api_key));
modpackApi = new ModpackSearchApi(context.getString(R.string.curseforge_api_key), mSearchFilters);
}

@Override
Expand Down Expand Up @@ -148,26 +154,85 @@ private void displayFilterDialog() {
TextView mSelectedVersion = dialog.findViewById(R.id.search_mod_selected_mc_version_textview);
Button mSelectVersionButton = dialog.findViewById(R.id.search_mod_mc_version_button);
Button mApplyButton = dialog.findViewById(R.id.search_mod_apply_filters);
Spinner mLoaderSpinner = dialog.findViewById(R.id.search_mod_loader_spinner);

assert mSelectVersionButton != null;
assert mSelectedVersion != null;
assert mApplyButton != null;

// Setup the expendable list behavior
mSelectVersionButton.setOnClickListener(v -> VersionSelectorDialog.open(v.getContext(), true, (id, snapshot)-> mSelectedVersion.setText(id)));
// Set up loader spinner
if (mLoaderSpinner != null) {
String[] loaderLabels = {"Any loader", "Fabric", "Forge", "Quilt", "NeoForge"};
final String[] loaderValues = {"", "fabric", "forge", "quilt", "neoforge"};
ArrayAdapter<String> loaderAdapter = new ArrayAdapter<>(
requireContext(), android.R.layout.simple_spinner_item, loaderLabels);
loaderAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mLoaderSpinner.setAdapter(loaderAdapter);

// Restore current selection
String currentLoader = mSearchFilters.modLoader != null ? mSearchFilters.modLoader : "";
for (int i = 0; i < loaderValues.length; i++) {
if (loaderValues[i].equals(currentLoader)) {
mLoaderSpinner.setSelection(i);
break;
}
}

mSelectVersionButton.setOnClickListener(v ->
VersionSelectorDialog.open(v.getContext(), true,
(id, snapshot) -> mSelectedVersion.setText(id)));

mSelectedVersion.setText(mSearchFilters.mcVersion);

mApplyButton.setOnClickListener(v -> {
mSearchFilters.mcVersion = mSelectedVersion.getText().toString();
int pos = mLoaderSpinner.getSelectedItemPosition();
mSearchFilters.modLoader = loaderValues[pos];
searchMods(mSearchEditText.getText().toString());
dialogInterface.dismiss();
});
} else {
mSelectVersionButton.setOnClickListener(v ->
VersionSelectorDialog.open(v.getContext(), true,
(id, snapshot) -> mSelectedVersion.setText(id)));
mSelectedVersion.setText(mSearchFilters.mcVersion);
mApplyButton.setOnClickListener(v -> {
mSearchFilters.mcVersion = mSelectedVersion.getText().toString();
searchMods(mSearchEditText.getText().toString());
dialogInterface.dismiss();
});
}
});

// Apply visually all the current settings
mSelectedVersion.setText(mSearchFilters.mcVersion);
dialog.show();
}

// Apply the new settings
mApplyButton.setOnClickListener(v -> {
mSearchFilters.mcVersion = mSelectedVersion.getText().toString();
searchMods(mSearchEditText.getText().toString());
dialogInterface.dismiss();
});
});
// ── ModpackSearchApi ──────────────────────────────────────────────────────

private static class ModpackSearchApi extends CommonApi {
private final SearchFilters mFilters;
private final ModrinthApi mModrinthApi = new ModrinthApi();

dialog.show();
ModpackSearchApi(String curseforgeApiKey, SearchFilters filters) {
super(curseforgeApiKey);
mFilters = filters;
}

/**
* Override getModDetails so the version dropdown only shows versions
* matching the selected MC version and loader filter.
*/
@Override
public ModDetail getModDetails(ModItem item) {
if (item.apiSource == Constants.SOURCE_MODRINTH) {
String filterVer = (mFilters.mcVersion != null && !mFilters.mcVersion.isEmpty())
? mFilters.mcVersion : null;
String filterLoader = (mFilters.modLoader != null && !mFilters.modLoader.isEmpty())
? mFilters.modLoader : null;
return mModrinthApi.getModDetails(item, filterVer, filterLoader);
}
// CurseForge: delegate normally (CF search already filters by version/loader)
return super.getModDetails(item);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,17 @@ public SearchResult searchMod(SearchFilters searchFilters, SearchResult previous
params.put("sortOrder", "desc");
if(searchFilters.mcVersion != null && !searchFilters.mcVersion.isEmpty())
params.put("gameVersion", searchFilters.mcVersion);
if(searchFilters.modLoader != null && !searchFilters.modLoader.isEmpty()) {
// CF modLoaderType: 1=Forge, 4=Fabric, 5=Quilt, 6=NeoForge
int modLoaderType = 0;
switch(searchFilters.modLoader.toLowerCase()) {
case "forge": modLoaderType = 1; break;
case "fabric": modLoaderType = 4; break;
case "quilt": modLoaderType = 5; break;
case "neoforge": modLoaderType = 6; break;
}
if(modLoaderType != 0) params.put("modLoaderType", modLoaderType);
}
if(previousPageResult != null)
params.put("index", curseforgeSearchResult.previousOffset);

Expand All @@ -79,6 +90,12 @@ public SearchResult searchMod(SearchFilters searchFilters, SearchResult previous
// Gson automatically casts null to false, which leans to issues
// So, only check the distribution flag if it is non-null
boolean restricted = !allowModDistribution.isJsonNull() && !allowModDistribution.getAsBoolean();
// For modpacks, skip restricted entries entirely (same as before)
// For individual mods, keep them so we can show the CF website dialog
if (restricted && searchFilters.isModpack) {
Log.i("CurseforgeApi", "Skipping modpack "+dataElement.get("name").getAsString() + " because curseforge sucks");
continue;
}
JsonObject logo = dataElement.getAsJsonObject("logo");
String thumbnailUrl = (logo != null && logo.has("thumbnailUrl") && !logo.get("thumbnailUrl").isJsonNull())
? logo.get("thumbnailUrl").getAsString() : "";
Expand All @@ -89,6 +106,16 @@ public SearchResult searchMod(SearchFilters searchFilters, SearchResult previous
dataElement.get("summary").getAsString(),
thumbnailUrl);
modItem.isRestricted = restricted;
// Capture the mod page URL from CF API for use in restriction dialog
JsonObject links = dataElement.getAsJsonObject("links");
if (links != null && links.has("websiteUrl") && !links.get("websiteUrl").isJsonNull()) {
modItem.websiteUrl = links.get("websiteUrl").getAsString();
} else {
// Fallback using slug if available, otherwise numeric id
String slug = dataElement.has("slug") && !dataElement.get("slug").isJsonNull()
? dataElement.get("slug").getAsString() : modItem.id;
modItem.websiteUrl = "https://www.curseforge.com/minecraft/mc-mods/" + slug;
}
modItemList.add(modItem);
}
if(curseforgeSearchResult == null) curseforgeSearchResult = new CurseforgeSearchResult();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public SearchResult searchMod(SearchFilters searchFilters, SearchResult previous
facetString.append(String.format("[\"project_type:%s\"]", searchFilters.isModpack ? "modpack" : "mod"));
if(searchFilters.mcVersion != null && !searchFilters.mcVersion.isEmpty())
facetString.append(String.format(",[\"versions:%s\"]", searchFilters.mcVersion));
if(searchFilters.modLoader != null && !searchFilters.modLoader.isEmpty())
facetString.append(String.format(",[\"categories:%s\"]", searchFilters.modLoader));
facetString.append("]");
params.put("facets", facetString.toString());
params.put("query", searchFilters.name);
Expand Down Expand Up @@ -86,14 +88,18 @@ public SearchResult searchMod(SearchFilters searchFilters, SearchResult previous

@Override
public ModDetail getModDetails(ModItem item) {
return getModDetails(item, null);
return getModDetails(item, null, null);
}

public ModDetail getModDetails(ModItem item, String filterMcVersion) {
return getModDetails(item, filterMcVersion, null);
}

public ModDetail getModDetails(ModItem item, String filterMcVersion, String filterLoader) {
JsonArray response = mApiHandler.get(String.format("project/%s/version", item.id), JsonArray.class);
if(response == null) return null;

// Collect versions, optionally filtering by MC version
// Collect versions, optionally filtering by MC version and/or loader
java.util.List<JsonObject> versions = new java.util.ArrayList<>();
for (int i = 0; i < response.size(); i++) {
JsonObject v = response.get(i).getAsJsonObject();
Expand All @@ -108,6 +114,17 @@ public ModDetail getModDetails(ModItem item, String filterMcVersion) {
}
if (!matches) continue;
}
if (filterLoader != null && !filterLoader.isEmpty()) {
JsonArray loaders = v.get("loaders").getAsJsonArray();
boolean matches = false;
for (int j = 0; j < loaders.size(); j++) {
if (filterLoader.equalsIgnoreCase(loaders.get(j).getAsString())) {
matches = true;
break;
}
}
if (!matches) continue;
}
versions.add(v);
}

Expand Down Expand Up @@ -209,4 +226,4 @@ private ModLoader installMrpack(File mrpackFile, File instanceDestination) throw
class ModrinthSearchResult extends SearchResult {
int previousOffset;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public ModDetail(ModItem item, String[] versionNames, String[] mcVersionNames, S
public ModDetail(ModItem item, String[] versionNames, String[] mcVersionNames, String[] versionUrls, String[] hashes,
String[][] depIds, String[][] depTypes) {
super(item.apiSource, item.isModpack, item.id, item.title, item.description, item.imageUrl);
this.isRestricted = item.isRestricted;
this.websiteUrl = item.websiteUrl;
this.versionNames = versionNames;
this.mcVersionNames = mcVersionNames;
this.versionUrls = versionUrls;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public class ModItem extends ModSource {
public String imageUrl;
/** True if the mod author blocked third-party distribution (CF allowModDistribution=false) */
public boolean isRestricted;
/** Direct website URL for the mod page (used for CF restricted mods) */
public String websiteUrl;

public ModItem(int apiSource, boolean isModpack, String id, String title, String description, String imageUrl) {
this.apiSource = apiSource;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ public class SearchFilters {
public boolean isModpack;
public String name;
@Nullable public String mcVersion;
/** Mod loader filter: "fabric", "forge", "quilt", "neoforge", or null/empty for any */
@Nullable public String modLoader;

}
}
Loading
Loading