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
Binary file modified app_pojavlauncher/src/main/assets/amethyst.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified app_pojavlauncher/src/main/ic_launcher-playstore.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ public class LauncherActivity extends BaseActivity {
if(data != null) {
PojavApplication.sExecutorService.execute(() -> {
try {
ModLoader loaderInfo = new CommonApi(getString(R.string.curseforge_api_key)).importModpack(this, data);
ModLoader loaderInfo = new CommonApi(
net.kdt.pojavlaunch.prefs.LauncherPreferences.resolveCurseforgeApiKey(this))
.importModpack(this, data);
if (loaderInfo == null) return;
loaderInfo.getDownloadTask(new NotificationDownloadListener(this, loaderInfo)).run();
} catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,24 @@ private void openModStore() {
openPaneFragment(fragment, ModsSearchFragment.TAG);
}

/**
* True if {@code tag} is already the top entry on the right pane's back stack —
* i.e. that fragment is already showing. Used to swallow a double-tap on the
* same trigger instead of pushing a second, identical back-stack entry (which
* made Back have to be pressed twice to actually leave).
*/
private boolean isTagAlreadyOnTop(String tag) {
if (!isTwoPane()) return false;
androidx.fragment.app.FragmentManager fm = getChildFragmentManager();
int count = fm.getBackStackEntryCount();
if (count == 0) return false;
return tag.equals(fm.getBackStackEntryAt(count - 1).getName());
}

/** Navigate to a pre-built fragment instance (preserves args on fresh instances). */
private void openPaneFragment(Fragment fragment, String tag) {
if (isTwoPane()) {
if (isTagAlreadyOnTop(tag)) return; // already showing — ignore the double-tap
getChildFragmentManager()
.beginTransaction()
.setReorderingAllowed(true)
Expand All @@ -179,6 +194,7 @@ private void openPaneFragment(Fragment fragment, String tag) {
private void openPane(Class<? extends Fragment> fragmentClass, String tag,
@Nullable Bundle args) {
if (isTwoPane()) {
if (isTagAlreadyOnTop(tag)) return; // already showing — ignore the double-tap
getChildFragmentManager()
.beginTransaction()
.setReorderingAllowed(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
Expand Down Expand Up @@ -37,6 +38,7 @@ public class ManageModsFragment extends Fragment {

private ImageButton mFilterButton;
private ImageButton mRefreshButton;
private ProgressBar mUpdateProgress;
private InstalledModAdapter mAdapter;

public ManageModsFragment() {
Expand All @@ -48,14 +50,15 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
ImageButton backButton = view.findViewById(R.id.manage_mods_back);
mFilterButton = view.findViewById(R.id.manage_mods_filter);
mRefreshButton = view.findViewById(R.id.manage_mods_refresh);
mUpdateProgress = view.findViewById(R.id.manage_mods_update_progress);
ImageButton addButton = view.findViewById(R.id.manage_mods_add);
TextView title = view.findViewById(R.id.manage_mods_title);
RecyclerView recycler = view.findViewById(R.id.manage_mods_recycler);
View emptyState = view.findViewById(R.id.manage_mods_empty);

backButton.setOnClickListener(v -> requireActivity().onBackPressed());
mFilterButton.setOnClickListener(v -> showFilterDialog());
mRefreshButton.setOnClickListener(v -> runUpdateCheck());
mRefreshButton.setOnClickListener(v -> runUpdateCheck(false));
addButton.setOnClickListener(v -> openModSearch());

String profileName = getCurrentProfileName();
Expand All @@ -81,19 +84,24 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
recycler.setLayoutManager(new LinearLayoutManager(requireContext()));
recycler.setAdapter(mAdapter);

// Update checking is opt-in: only runs when the user taps the refresh
// button (see mRefreshButton's listener above). Auto-checking here used
// to fire a network call per mod the instant this screen opened, which
// also fought with the initial icon-resolution pass and made icons
// flash to the placeholder glyph.
// Auto-check for updates as soon as the screen opens, in addition to
// the manual refresh button. Silent (no "checking…" toast, and no
// "set a filter first" toast) since this fires automatically — if
// there's no filter set yet, it just quietly does nothing until the
// user configures one and hits refresh manually.
runUpdateCheck(true);
}

/**
* Runs the Modrinth update check across all installed mods for this instance.
* Only ever triggered by the user tapping the refresh button — update
* checking is fully opt-in, never automatic.
* Triggered automatically when the screen opens (silent = true) and manually
* via the refresh button (silent = false).
*
* @param silent when true, skips the "checking…"/"set a filter first" toasts —
* used for the automatic on-open check so it doesn't nag the
* user if they haven't configured a version/loader filter yet.
*/
private void runUpdateCheck() {
private void runUpdateCheck(boolean silent) {
if (mAdapter == null) return;

String profileKey = getCurrentProfileKey();
Expand All @@ -103,17 +111,31 @@ private void runUpdateCheck() {
String loader = prefs.getString(KEY_LOADER + profileKey, "");

if (version.isEmpty() && loader.isEmpty()) {
Toast.makeText(requireContext(),
R.string.mod_update_no_filter, Toast.LENGTH_SHORT).show();
if (!silent) {
Toast.makeText(requireContext(),
R.string.mod_update_no_filter, Toast.LENGTH_SHORT).show();
}
return;
}

mAdapter.setFilter(version, loader);

// While checking: hide the refresh button and show just a spinner in
// its place. No per-mod update button appears until its own check
// resolves, so this spinner is the only thing indicating progress.
mRefreshButton.setEnabled(false);
Toast.makeText(requireContext(), R.string.mod_update_checking, Toast.LENGTH_SHORT).show();
mRefreshButton.setVisibility(View.GONE);
if (mUpdateProgress != null) mUpdateProgress.setVisibility(View.VISIBLE);

if (!silent) {
Toast.makeText(requireContext(), R.string.mod_update_checking, Toast.LENGTH_SHORT).show();
}

mAdapter.checkForUpdates(() -> mRefreshButton.setEnabled(true));
mAdapter.checkForUpdates(() -> {
mRefreshButton.setEnabled(true);
mRefreshButton.setVisibility(View.VISIBLE);
if (mUpdateProgress != null) mUpdateProgress.setVisibility(View.GONE);
});
}

// ── Filter dialog ────────────────────────────────────────────────────────
Expand All @@ -136,6 +158,15 @@ private void showFilterDialog() {
Button applyButton = dialog.findViewById(R.id.search_mod_apply_filters);
Spinner loaderSpinner = dialog.findViewById(R.id.search_mod_loader_spinner);

// This dialog is shared with the mod/modpack search screens, which also
// show a Modrinth/CurseForge/Both engine picker — not applicable here,
// since Manage Mods just filters already-installed mods by version/loader
// for update checking, it doesn't pick a search engine.
View engineLabel = dialog.findViewById(R.id.search_mod_engine_textview);
View engineSpinner = dialog.findViewById(R.id.search_mod_engine_spinner);
if (engineLabel != null) engineLabel.setVisibility(View.GONE);
if (engineSpinner != null) engineSpinner.setVisibility(View.GONE);

if (versionText == null || selectVersion == null || applyButton == null) return;

versionText.setText(savedVersion);
Expand Down
Loading
Loading