Skip to content

Commit 1080c84

Browse files
authored
Merge pull request #66 from CopperLauncher/Feat/Loaders
Feat/loaders
2 parents 54113c1 + 0f4e291 commit 1080c84

6 files changed

Lines changed: 184 additions & 37 deletions

File tree

app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ModsSearchFragment.java

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -175,22 +175,57 @@ private void displayFilterDialog() {
175175
TextView mSelectedVersion = dialog.findViewById(R.id.search_mod_selected_mc_version_textview);
176176
Button mSelectVersionButton = dialog.findViewById(R.id.search_mod_mc_version_button);
177177
Button mApplyButton = dialog.findViewById(R.id.search_mod_apply_filters);
178+
android.widget.Spinner mLoaderSpinner = dialog.findViewById(R.id.search_mod_loader_spinner);
178179

179180
assert mSelectedVersion != null;
180181
assert mSelectVersionButton != null;
181182
assert mApplyButton != null;
182183

183-
mSelectVersionButton.setOnClickListener(v ->
184-
VersionSelectorDialog.open(v.getContext(), true,
185-
(id, snapshot) -> mSelectedVersion.setText(id)));
184+
// Set up loader spinner
185+
if (mLoaderSpinner != null) {
186+
String[] loaderLabels = {"Any loader", "Fabric", "Forge", "Quilt", "NeoForge"};
187+
final String[] loaderValues = {"", "fabric", "forge", "quilt", "neoforge"};
188+
android.widget.ArrayAdapter<String> loaderAdapter = new android.widget.ArrayAdapter<>(
189+
requireContext(), android.R.layout.simple_spinner_item, loaderLabels);
190+
loaderAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
191+
mLoaderSpinner.setAdapter(loaderAdapter);
192+
193+
// Restore current selection
194+
String currentLoader = mSearchFilters.modLoader != null ? mSearchFilters.modLoader : "";
195+
for (int i = 0; i < loaderValues.length; i++) {
196+
if (loaderValues[i].equals(currentLoader)) {
197+
mLoaderSpinner.setSelection(i);
198+
break;
199+
}
200+
}
186201

187-
mSelectedVersion.setText(mSearchFilters.mcVersion);
202+
mSelectVersionButton.setOnClickListener(v ->
203+
VersionSelectorDialog.open(v.getContext(), true,
204+
(id, snapshot) -> mSelectedVersion.setText(id)));
188205

189-
mApplyButton.setOnClickListener(v -> {
190-
mSearchFilters.mcVersion = mSelectedVersion.getText().toString();
191-
searchMods(mSearchEditText.getText().toString());
192-
dialogInterface.dismiss();
193-
});
206+
mSelectedVersion.setText(mSearchFilters.mcVersion);
207+
208+
mApplyButton.setOnClickListener(v -> {
209+
mSearchFilters.mcVersion = mSelectedVersion.getText().toString();
210+
int pos = mLoaderSpinner.getSelectedItemPosition();
211+
mSearchFilters.modLoader = loaderValues[pos];
212+
searchMods(mSearchEditText.getText().toString());
213+
dialogInterface.dismiss();
214+
});
215+
} else {
216+
// Fallback if spinner view not found
217+
mSelectVersionButton.setOnClickListener(v ->
218+
VersionSelectorDialog.open(v.getContext(), true,
219+
(id, snapshot) -> mSelectedVersion.setText(id)));
220+
221+
mSelectedVersion.setText(mSearchFilters.mcVersion);
222+
223+
mApplyButton.setOnClickListener(v -> {
224+
mSearchFilters.mcVersion = mSelectedVersion.getText().toString();
225+
searchMods(mSearchEditText.getText().toString());
226+
dialogInterface.dismiss();
227+
});
228+
}
194229
});
195230

196231
dialog.show();
@@ -219,7 +254,9 @@ public ModDetail getModDetails(ModItem item) {
219254
if (item.apiSource == net.kdt.pojavlaunch.modloaders.modpacks.models.Constants.SOURCE_MODRINTH) {
220255
String filterVer = (mFilters.mcVersion != null && !mFilters.mcVersion.isEmpty())
221256
? mFilters.mcVersion : null;
222-
return mModrinthApi.getModDetails(item, filterVer);
257+
String filterLoader = (mFilters.modLoader != null && !mFilters.modLoader.isEmpty())
258+
? mFilters.modLoader : null;
259+
return mModrinthApi.getModDetails(item, filterVer, filterLoader);
223260
}
224261
return super.getModDetails(item);
225262
}
@@ -359,10 +396,12 @@ private void downloadDependency(String projectId, File modsDir) {
359396
try {
360397
String filterVer = (mFilters.mcVersion != null && !mFilters.mcVersion.isEmpty())
361398
? mFilters.mcVersion : "";
399+
String filterLoader = (mFilters.modLoader != null && !mFilters.modLoader.isEmpty())
400+
? mFilters.modLoader : null;
362401
ModItem depItem = new ModItem(
363402
net.kdt.pojavlaunch.modloaders.modpacks.models.Constants.SOURCE_MODRINTH,
364403
false, projectId, projectId, "", "");
365-
ModDetail depDetail = mModrinthApi.getModDetails(depItem, filterVer.isEmpty() ? null : filterVer);
404+
ModDetail depDetail = mModrinthApi.getModDetails(depItem, filterVer.isEmpty() ? null : filterVer, filterLoader);
366405
if (depDetail == null || depDetail.versionUrls == null || depDetail.versionUrls.length == 0) return;
367406

368407
String depUrl = depDetail.versionUrls[0];

app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/SearchModFragment.java

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
import android.graphics.Color;
66
import android.os.Bundle;
77
import android.view.View;
8+
import android.widget.ArrayAdapter;
89
import android.widget.Button;
910
import android.widget.EditText;
1011
import android.widget.ImageButton;
1112
import android.widget.ProgressBar;
13+
import android.widget.Spinner;
1214
import android.widget.TextView;
1315

1416
import androidx.annotation.NonNull;
@@ -23,6 +25,10 @@
2325
import net.kdt.pojavlaunch.modloaders.modpacks.ModItemAdapter;
2426
import net.kdt.pojavlaunch.modloaders.modpacks.api.CommonApi;
2527
import net.kdt.pojavlaunch.modloaders.modpacks.api.ModpackApi;
28+
import net.kdt.pojavlaunch.modloaders.modpacks.api.ModrinthApi;
29+
import net.kdt.pojavlaunch.modloaders.modpacks.models.Constants;
30+
import net.kdt.pojavlaunch.modloaders.modpacks.models.ModDetail;
31+
import net.kdt.pojavlaunch.modloaders.modpacks.models.ModItem;
2632
import net.kdt.pojavlaunch.modloaders.modpacks.models.SearchFilters;
2733
import net.kdt.pojavlaunch.profiles.VersionSelectorDialog;
2834
import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper;
@@ -61,7 +67,7 @@ public SearchModFragment(){
6167
@Override
6268
public void onAttach(@NonNull Context context) {
6369
super.onAttach(context);
64-
modpackApi = new CommonApi(context.getString(R.string.curseforge_api_key));
70+
modpackApi = new ModpackSearchApi(context.getString(R.string.curseforge_api_key), mSearchFilters);
6571
}
6672

6773
@Override
@@ -148,26 +154,85 @@ private void displayFilterDialog() {
148154
TextView mSelectedVersion = dialog.findViewById(R.id.search_mod_selected_mc_version_textview);
149155
Button mSelectVersionButton = dialog.findViewById(R.id.search_mod_mc_version_button);
150156
Button mApplyButton = dialog.findViewById(R.id.search_mod_apply_filters);
157+
Spinner mLoaderSpinner = dialog.findViewById(R.id.search_mod_loader_spinner);
151158

152159
assert mSelectVersionButton != null;
153160
assert mSelectedVersion != null;
154161
assert mApplyButton != null;
155162

156-
// Setup the expendable list behavior
157-
mSelectVersionButton.setOnClickListener(v -> VersionSelectorDialog.open(v.getContext(), true, (id, snapshot)-> mSelectedVersion.setText(id)));
163+
// Set up loader spinner
164+
if (mLoaderSpinner != null) {
165+
String[] loaderLabels = {"Any loader", "Fabric", "Forge", "Quilt", "NeoForge"};
166+
final String[] loaderValues = {"", "fabric", "forge", "quilt", "neoforge"};
167+
ArrayAdapter<String> loaderAdapter = new ArrayAdapter<>(
168+
requireContext(), android.R.layout.simple_spinner_item, loaderLabels);
169+
loaderAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
170+
mLoaderSpinner.setAdapter(loaderAdapter);
171+
172+
// Restore current selection
173+
String currentLoader = mSearchFilters.modLoader != null ? mSearchFilters.modLoader : "";
174+
for (int i = 0; i < loaderValues.length; i++) {
175+
if (loaderValues[i].equals(currentLoader)) {
176+
mLoaderSpinner.setSelection(i);
177+
break;
178+
}
179+
}
180+
181+
mSelectVersionButton.setOnClickListener(v ->
182+
VersionSelectorDialog.open(v.getContext(), true,
183+
(id, snapshot) -> mSelectedVersion.setText(id)));
184+
185+
mSelectedVersion.setText(mSearchFilters.mcVersion);
186+
187+
mApplyButton.setOnClickListener(v -> {
188+
mSearchFilters.mcVersion = mSelectedVersion.getText().toString();
189+
int pos = mLoaderSpinner.getSelectedItemPosition();
190+
mSearchFilters.modLoader = loaderValues[pos];
191+
searchMods(mSearchEditText.getText().toString());
192+
dialogInterface.dismiss();
193+
});
194+
} else {
195+
mSelectVersionButton.setOnClickListener(v ->
196+
VersionSelectorDialog.open(v.getContext(), true,
197+
(id, snapshot) -> mSelectedVersion.setText(id)));
198+
mSelectedVersion.setText(mSearchFilters.mcVersion);
199+
mApplyButton.setOnClickListener(v -> {
200+
mSearchFilters.mcVersion = mSelectedVersion.getText().toString();
201+
searchMods(mSearchEditText.getText().toString());
202+
dialogInterface.dismiss();
203+
});
204+
}
205+
});
158206

159-
// Apply visually all the current settings
160-
mSelectedVersion.setText(mSearchFilters.mcVersion);
207+
dialog.show();
208+
}
161209

162-
// Apply the new settings
163-
mApplyButton.setOnClickListener(v -> {
164-
mSearchFilters.mcVersion = mSelectedVersion.getText().toString();
165-
searchMods(mSearchEditText.getText().toString());
166-
dialogInterface.dismiss();
167-
});
168-
});
210+
// ── ModpackSearchApi ──────────────────────────────────────────────────────
169211

212+
private static class ModpackSearchApi extends CommonApi {
213+
private final SearchFilters mFilters;
214+
private final ModrinthApi mModrinthApi = new ModrinthApi();
170215

171-
dialog.show();
216+
ModpackSearchApi(String curseforgeApiKey, SearchFilters filters) {
217+
super(curseforgeApiKey);
218+
mFilters = filters;
219+
}
220+
221+
/**
222+
* Override getModDetails so the version dropdown only shows versions
223+
* matching the selected MC version and loader filter.
224+
*/
225+
@Override
226+
public ModDetail getModDetails(ModItem item) {
227+
if (item.apiSource == Constants.SOURCE_MODRINTH) {
228+
String filterVer = (mFilters.mcVersion != null && !mFilters.mcVersion.isEmpty())
229+
? mFilters.mcVersion : null;
230+
String filterLoader = (mFilters.modLoader != null && !mFilters.modLoader.isEmpty())
231+
? mFilters.modLoader : null;
232+
return mModrinthApi.getModDetails(item, filterVer, filterLoader);
233+
}
234+
// CurseForge: delegate normally (CF search already filters by version/loader)
235+
return super.getModDetails(item);
236+
}
172237
}
173-
}
238+
}

app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/CurseforgeApi.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,17 @@ public SearchResult searchMod(SearchFilters searchFilters, SearchResult previous
6464
params.put("sortOrder", "desc");
6565
if(searchFilters.mcVersion != null && !searchFilters.mcVersion.isEmpty())
6666
params.put("gameVersion", searchFilters.mcVersion);
67+
if(searchFilters.modLoader != null && !searchFilters.modLoader.isEmpty()) {
68+
// CF modLoaderType: 1=Forge, 4=Fabric, 5=Quilt, 6=NeoForge
69+
int modLoaderType = 0;
70+
switch(searchFilters.modLoader.toLowerCase()) {
71+
case "forge": modLoaderType = 1; break;
72+
case "fabric": modLoaderType = 4; break;
73+
case "quilt": modLoaderType = 5; break;
74+
case "neoforge": modLoaderType = 6; break;
75+
}
76+
if(modLoaderType != 0) params.put("modLoaderType", modLoaderType);
77+
}
6778
if(previousPageResult != null)
6879
params.put("index", curseforgeSearchResult.previousOffset);
6980

app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/api/ModrinthApi.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ public SearchResult searchMod(SearchFilters searchFilters, SearchResult previous
5252
facetString.append(String.format("[\"project_type:%s\"]", searchFilters.isModpack ? "modpack" : "mod"));
5353
if(searchFilters.mcVersion != null && !searchFilters.mcVersion.isEmpty())
5454
facetString.append(String.format(",[\"versions:%s\"]", searchFilters.mcVersion));
55+
if(searchFilters.modLoader != null && !searchFilters.modLoader.isEmpty())
56+
facetString.append(String.format(",[\"categories:%s\"]", searchFilters.modLoader));
5557
facetString.append("]");
5658
params.put("facets", facetString.toString());
5759
params.put("query", searchFilters.name);
@@ -86,14 +88,18 @@ public SearchResult searchMod(SearchFilters searchFilters, SearchResult previous
8688

8789
@Override
8890
public ModDetail getModDetails(ModItem item) {
89-
return getModDetails(item, null);
91+
return getModDetails(item, null, null);
9092
}
9193

9294
public ModDetail getModDetails(ModItem item, String filterMcVersion) {
95+
return getModDetails(item, filterMcVersion, null);
96+
}
97+
98+
public ModDetail getModDetails(ModItem item, String filterMcVersion, String filterLoader) {
9399
JsonArray response = mApiHandler.get(String.format("project/%s/version", item.id), JsonArray.class);
94100
if(response == null) return null;
95101

96-
// Collect versions, optionally filtering by MC version
102+
// Collect versions, optionally filtering by MC version and/or loader
97103
java.util.List<JsonObject> versions = new java.util.ArrayList<>();
98104
for (int i = 0; i < response.size(); i++) {
99105
JsonObject v = response.get(i).getAsJsonObject();
@@ -108,6 +114,17 @@ public ModDetail getModDetails(ModItem item, String filterMcVersion) {
108114
}
109115
if (!matches) continue;
110116
}
117+
if (filterLoader != null && !filterLoader.isEmpty()) {
118+
JsonArray loaders = v.get("loaders").getAsJsonArray();
119+
boolean matches = false;
120+
for (int j = 0; j < loaders.size(); j++) {
121+
if (filterLoader.equalsIgnoreCase(loaders.get(j).getAsString())) {
122+
matches = true;
123+
break;
124+
}
125+
}
126+
if (!matches) continue;
127+
}
111128
versions.add(v);
112129
}
113130

@@ -209,4 +226,4 @@ private ModLoader installMrpack(File mrpackFile, File instanceDestination) throw
209226
class ModrinthSearchResult extends SearchResult {
210227
int previousOffset;
211228
}
212-
}
229+
}

app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/modpacks/models/SearchFilters.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,7 @@ public class SearchFilters {
99
public boolean isModpack;
1010
public String name;
1111
@Nullable public String mcVersion;
12+
/** Mod loader filter: "fabric", "forge", "quilt", "neoforge", or null/empty for any */
13+
@Nullable public String modLoader;
1214

13-
}
15+
}

app_pojavlauncher/src/main/res/layout/dialog_mod_filters.xml

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,17 @@
1616
android:layout_height="wrap_content"
1717
android:text="@string/profiles_profile_version"
1818
app:layout_constraintTop_toTopOf="parent"
19-
2019
app:layout_constraintStart_toStartOf="@+id/search_mod_selected_mc_version_textview" />
2120

2221
<TextView
2322
android:id="@+id/search_mod_selected_mc_version_textview"
2423
android:layout_width="0dp"
2524
android:layout_height="wrap_content"
26-
2725
android:layout_marginEnd="@dimen/padding_medium"
28-
2926
android:background="@drawable/background_line"
3027
android:hint="@string/version_select_hint"
3128
android:paddingHorizontal="@dimen/padding_heavy"
3229
android:textSize="@dimen/_13ssp"
33-
3430
app:layout_constraintEnd_toStartOf="@+id/search_mod_mc_version_button"
3531
app:layout_constraintStart_toStartOf="parent"
3632
app:layout_constraintTop_toBottomOf="@id/search_mod_mc_version_textview" />
@@ -40,22 +36,39 @@
4036
android:layout_width="wrap_content"
4137
android:layout_height="0dp"
4238
android:paddingHorizontal="@dimen/padding_large"
43-
4439
android:text="@string/global_select"
45-
4640
app:layout_constraintBottom_toBottomOf="@+id/search_mod_selected_mc_version_textview"
4741
app:layout_constraintEnd_toEndOf="parent"
4842
app:layout_constraintTop_toTopOf="@+id/search_mod_selected_mc_version_textview" />
4943

44+
<!-- Loader filter -->
45+
<TextView
46+
android:id="@+id/search_mod_loader_textview"
47+
android:layout_width="wrap_content"
48+
android:layout_height="wrap_content"
49+
android:text="Mod Loader"
50+
android:layout_marginTop="@dimen/padding_medium"
51+
app:layout_constraintTop_toBottomOf="@id/search_mod_selected_mc_version_textview"
52+
app:layout_constraintStart_toStartOf="parent" />
53+
54+
<Spinner
55+
android:id="@+id/search_mod_loader_spinner"
56+
android:layout_width="0dp"
57+
android:layout_height="wrap_content"
58+
android:background="@drawable/background_line"
59+
android:paddingHorizontal="@dimen/padding_heavy"
60+
app:layout_constraintTop_toBottomOf="@id/search_mod_loader_textview"
61+
app:layout_constraintStart_toStartOf="parent"
62+
app:layout_constraintEnd_toEndOf="parent" />
63+
5064
<!-- Apply -->
5165
<com.kdt.mcgui.MineButton
5266
android:id="@+id/search_mod_apply_filters"
5367
android:layout_width="match_parent"
5468
android:layout_height="wrap_content"
5569
android:text="@string/generic_apply"
5670
android:layout_marginVertical="@dimen/padding_large"
57-
58-
app:layout_constraintTop_toBottomOf="@id/search_mod_selected_mc_version_textview"
71+
app:layout_constraintTop_toBottomOf="@id/search_mod_loader_spinner"
5972
/>
6073

6174
</androidx.constraintlayout.widget.ConstraintLayout>

0 commit comments

Comments
 (0)