diff --git a/app/build.gradle b/app/build.gradle index 5d9a3ef..dabf4cb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -82,7 +82,7 @@ android { dependencies { implementation 'androidx.appcompat:appcompat:1.7.1' - implementation 'com.google.android.material:material:1.12.0' + implementation 'com.google.android.material:material:1.14.0-alpha04' implementation 'androidx.constraintlayout:constraintlayout:2.2.1' implementation 'androidx.documentfile:documentfile:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.4.0' diff --git a/app/src/main/java/com/izzy2lost/psx2/GamesCoverDialogFragment.java b/app/src/main/java/com/izzy2lost/psx2/GamesCoverDialogFragment.java index 8ec6280..ae2d541 100644 --- a/app/src/main/java/com/izzy2lost/psx2/GamesCoverDialogFragment.java +++ b/app/src/main/java/com/izzy2lost/psx2/GamesCoverDialogFragment.java @@ -26,6 +26,9 @@ import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.PagerSnapHelper; import androidx.recyclerview.widget.RecyclerView; +import com.google.android.material.button.MaterialButtonToggleGroup; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import androidx.core.view.GravityCompat; import java.util.Locale; import java.util.ArrayList; import java.util.Arrays; @@ -374,13 +377,93 @@ public class GamesCoverDialogFragment extends DialogFragment { }); View btnHome = root.findViewById(R.id.btn_home); - if (btnHome != null) btnHome.setOnClickListener(v -> dismissAllowingStateLoss()); + if (btnHome != null) btnHome.setOnClickListener(v -> { + try { + androidx.drawerlayout.widget.DrawerLayout drawer = root.findViewById(R.id.dlg_drawer_layout); + if (drawer != null) drawer.openDrawer(GravityCompat.START); + } catch (Throwable ignored) {} + }); + + // Wire in-dialog navigation header actions to mirror main drawer + try { + com.google.android.material.navigation.NavigationView nav = root.findViewById(R.id.dialog_nav_view); + if (nav != null) { + View header = (nav.getHeaderCount() > 0) ? nav.getHeaderView(0) : nav.inflateHeaderView(R.layout.drawer_header_settings); + if (header != null) { + View btnPower = header.findViewById(R.id.drawer_btn_power); + if (btnPower != null) { + btnPower.setOnClickListener(v -> { + new MaterialAlertDialogBuilder(requireContext(), + com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog) + .setTitle("Power Off") + .setMessage("Quit the app?") + .setNegativeButton("Cancel", null) + .setPositiveButton("Quit", (d,w) -> { try { NativeApp.shutdown(); } catch (Throwable ignored) {} if (getActivity()!=null){ getActivity().finishAffinity(); getActivity().finishAndRemoveTask(); } System.exit(0); }) + .show(); + }); + } + View btnReboot = header.findViewById(R.id.drawer_btn_reboot); + if (btnReboot != null) { + btnReboot.setOnClickListener(v -> { + new MaterialAlertDialogBuilder(requireContext(), + com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog) + .setTitle("Reboot") + .setMessage("Restart the current game?") + .setNegativeButton("Cancel", null) + .setPositiveButton("Reboot", (d,w) -> { if (getActivity() instanceof MainActivity) { ((MainActivity) getActivity()).rebootEmu(); } }) + .show(); + }); + } + MaterialButtonToggleGroup tg = header.findViewById(R.id.drawer_tg_renderer); + View tbAt = header.findViewById(R.id.drawer_tb_at); + View tbVk = header.findViewById(R.id.drawer_tb_vk); + View tbGl = header.findViewById(R.id.drawer_tb_gl); + View tbSw = header.findViewById(R.id.drawer_tb_sw); + if (tg != null) { + int current = -1; + try { current = NativeApp.getCurrentRenderer(); } catch (Throwable ignored) {} + if (current == 14 && tbVk != null) tg.check(tbVk.getId()); + else if (current == 12 && tbGl != null) tg.check(tbGl.getId()); + else if (current == 13 && tbSw != null) tg.check(tbSw.getId()); + else if (tbAt != null) tg.check(tbAt.getId()); + tg.addOnButtonCheckedListener((group, checkedId, isChecked) -> { + if (!isChecked) return; + int r = -1; + if (checkedId == (tbVk != null ? tbVk.getId() : -2)) r = 14; + else if (checkedId == (tbGl != null ? tbGl.getId() : -2)) r = 12; + else if (checkedId == (tbSw != null ? tbSw.getId() : -2)) r = 13; + else r = -1; + try { + requireContext().getSharedPreferences("app_prefs", Context.MODE_PRIVATE).edit().putInt("renderer", r).apply(); + NativeApp.renderGpu(r); + } catch (Throwable ignored) {} + }); + } + View btnGameState = header.findViewById(R.id.drawer_btn_game_state); + if (btnGameState != null) { + btnGameState.setOnClickListener(v -> { + try { new SavesDialogFragment().show(getParentFragmentManager(), "saves_dialog"); } catch (Throwable ignored) {} + }); + } + } + } + } catch (Throwable ignored) {} View btnDownload = root.findViewById(R.id.btn_download); if (btnDownload != null) btnDownload.setOnClickListener(v -> startDownloadCovers()); return root; } + // Public API to open the in-dialog navigation drawer from other components + public void openDialogDrawer() { + try { + View root = getView(); + if (root == null) return; + androidx.drawerlayout.widget.DrawerLayout drawer = root.findViewById(R.id.dlg_drawer_layout); + if (drawer != null) drawer.openDrawer(androidx.core.view.GravityCompat.START); + } catch (Throwable ignored) {} + } + private void buildLettersAndBind() { letters.clear(); boolean hasHash = false; diff --git a/app/src/main/java/com/izzy2lost/psx2/MainActivity.java b/app/src/main/java/com/izzy2lost/psx2/MainActivity.java index f3aca04..f1e5180 100644 --- a/app/src/main/java/com/izzy2lost/psx2/MainActivity.java +++ b/app/src/main/java/com/izzy2lost/psx2/MainActivity.java @@ -38,6 +38,8 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.documentfile.provider.DocumentFile; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.core.content.ContextCompat; +import androidx.drawerlayout.widget.DrawerLayout; +import androidx.core.view.GravityCompat; import androidx.activity.OnBackPressedCallback; import com.google.android.material.button.MaterialButton; @@ -50,6 +52,8 @@ import android.provider.OpenableColumns; import org.json.JSONArray; import org.json.JSONException; import androidx.fragment.app.FragmentManager; +import com.google.android.material.navigation.NavigationView; +import com.google.android.material.button.MaterialButtonToggleGroup; import java.util.List; import java.util.Locale; @@ -99,7 +103,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF // Views present in both layouts View quick = findViewById(R.id.ll_quick_actions); View btnSettings = findViewById(R.id.btn_settings); - View btnControls = findViewById(R.id.btn_toggle_controls); + // Visibility toggle removed; no dependency on it for constraints View llJoy = findViewById(R.id.ll_pad_joy); View llDpad = findViewById(R.id.ll_pad_dpad); View llRight = findViewById(R.id.ll_pad_right_buttons); @@ -107,17 +111,16 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF // Use helper dp() - if (quick != null && btnSettings != null && btnControls != null) { + if (quick != null && btnSettings != null) { ConstraintLayout.LayoutParams lp = safeCLP(quick); if (orientation == Configuration.ORIENTATION_LANDSCAPE) { - // Between settings and controls, top-aligned + // Top-aligned row stretching from settings to end lp.width = 0; // chain between start/end lp.topToTop = btnSettings.getId(); lp.topToBottom = ConstraintLayout.LayoutParams.UNSET; lp.startToEnd = btnSettings.getId(); - lp.endToStart = btnControls.getId(); + lp.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID; lp.startToStart = ConstraintLayout.LayoutParams.UNSET; - lp.endToEnd = ConstraintLayout.LayoutParams.UNSET; lp.topMargin = dp(0); lp.horizontalChainStyle = ConstraintLayout.LayoutParams.CHAIN_PACKED; } else { @@ -423,6 +426,21 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF } updateUiForControllerPresence(); + // After initial BIOS auto-boot, gently open the Games dialog + // Only when setup is complete and this is app launch (not rotation) + if (getSharedPreferences("app_prefs", MODE_PRIVATE).getBoolean("first_run_done", false)) { + try { + final View decor = (getWindow() != null) ? getWindow().getDecorView() : null; + if (decor != null) { + decor.postDelayed(() -> { + if (!isFinishing() && !mSetupWizardActive) { + openGamesDialog(); + } + }, 1600); // small delay to let BIOS boot briefly + } + } catch (Throwable ignored) {} + } + // Show first-run setup wizard if needed if (!getSharedPreferences("app_prefs", MODE_PRIVATE).getBoolean("first_run_done", false)) { SetupWizardDialogFragment f = SetupWizardDialogFragment.newInstance(); @@ -457,86 +475,85 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF } private void makeButtonTouch() { - MaterialButton btn_file = findViewById(R.id.btn_file); - if(btn_file != null) { - // Tap: show games list (from persisted folder). If none, prompt to pick folder. - btn_file.setOnClickListener(v -> { - SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE); - String folderUri = prefs.getString("games_folder_uri", null); - if (TextUtils.isEmpty(folderUri)) { - pickGamesFolder(); - return; - } - showGamesListOrReselect(Uri.parse(folderUri)); - }); - // Long-press: reselect games folder - btn_file.setOnLongClickListener(v -> { - pickGamesFolder(); - return true; - }); - } + // SAVES button removed from main UI; access Save States via drawer or Quick Actions - // Combined saves dialog - MaterialButton btn_saves = findViewById(R.id.btn_saves); - if(btn_saves != null) { - btn_saves.setOnClickListener(v -> { - SavesDialogFragment dialog = new SavesDialogFragment(); - dialog.show(getSupportFragmentManager(), "saves_dialog"); - }); - // Long-press: choose SAF data folder for app files (covers/resources/etc) - btn_saves.setOnLongClickListener(v -> { - pickDataRootFolder(); - return true; - }); - } - // BIOS button repurposed: short tap toggles renderer, long-press picks BIOS folder - MaterialButton btn_bios = findViewById(R.id.btn_bios); - if (btn_bios != null) { - btn_bios.setOnClickListener(v -> { - int current = getCurrentRendererPref(); - int next; - // Cycle: AUTO(-1) -> VK(14) -> OGL(12) -> SW(13) -> AUTO - if (current == -1) next = 14; - else if (current == 14) next = 12; - else if (current == 12) next = 13; - else next = -1; - setRendererAndSave(next); - }); - btn_bios.setOnLongClickListener(v -> { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION - | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION - | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); - startActivityResultBiosFolderPick.launch(intent); - return true; - }); - } - - // Settings button opens dialog + // Menu button opens left drawer MaterialButton btn_settings = findViewById(R.id.btn_settings); if (btn_settings != null) { btn_settings.setOnClickListener(v -> { - FragmentManager fm = getSupportFragmentManager(); - SettingsDialogFragment dialog = new SettingsDialogFragment(); - dialog.show(fm, "settings_dialog"); - }); - // Long-press to open Cheats manager - btn_settings.setOnLongClickListener(v -> { - FragmentManager fm2 = getSupportFragmentManager(); - CheatsDialogFragment cd = new CheatsDialogFragment(); - cd.show(fm2, "cheats_dialog"); - return true; - }); - } - - // Toggle all UI visibility (including controls) - MaterialButton btnToggleControls = findViewById(R.id.btn_toggle_controls); - if (btnToggleControls != null) { - btnToggleControls.setOnClickListener(v -> { - toggleAllUIVisibility(); + try { + DrawerLayout drawer = findViewById(R.id.drawer_layout); + if (drawer != null) drawer.openDrawer(androidx.core.view.GravityCompat.START); + } catch (Throwable ignored) {} }); } + // Wire left drawer header controls (title/power/reboot/renderer) + try { + NavigationView nav = findViewById(R.id.nav_view); + if (nav != null) { + View header = (nav.getHeaderCount() > 0) ? nav.getHeaderView(0) : nav.inflateHeaderView(R.layout.drawer_header_settings); + if (header != null) { + View btnPower = header.findViewById(R.id.drawer_btn_power); + if (btnPower != null) { + btnPower.setOnClickListener(v -> { + new MaterialAlertDialogBuilder(this) + .setTitle("Power Off") + .setMessage("Quit the app?") + .setNegativeButton("Cancel", null) + .setPositiveButton("Quit", (d,w) -> { try { NativeApp.shutdown(); } catch (Throwable ignored) {} finishAffinity(); finishAndRemoveTask(); System.exit(0); }) + .show(); + }); + } + View btnReboot = header.findViewById(R.id.drawer_btn_reboot); + if (btnReboot != null) { + btnReboot.setOnClickListener(v -> { + new MaterialAlertDialogBuilder(this) + .setTitle("Reboot") + .setMessage("Restart the current game?") + .setNegativeButton("Cancel", null) + .setPositiveButton("Reboot", (d,w) -> rebootEmu()) + .show(); + }); + } + MaterialButtonToggleGroup tg = header.findViewById(R.id.drawer_tg_renderer); + View btnGameState = header.findViewById(R.id.drawer_btn_game_state); + View tbAt = header.findViewById(R.id.drawer_tb_at); + View tbVk = header.findViewById(R.id.drawer_tb_vk); + View tbGl = header.findViewById(R.id.drawer_tb_gl); + View tbSw = header.findViewById(R.id.drawer_tb_sw); + if (tg != null) { + int current = -1; + try { current = NativeApp.getCurrentRenderer(); } catch (Throwable ignored) {} + if (current == 14 && tbVk != null) tg.check(tbVk.getId()); + else if (current == 12 && tbGl != null) tg.check(tbGl.getId()); + else if (current == 13 && tbSw != null) tg.check(tbSw.getId()); + else if (tbAt != null) tg.check(tbAt.getId()); + tg.addOnButtonCheckedListener((group, checkedId, isChecked) -> { + if (!isChecked) return; + int r = -1; + if (checkedId == (tbVk != null ? tbVk.getId() : -2)) r = 14; + else if (checkedId == (tbGl != null ? tbGl.getId() : -2)) r = 12; + else if (checkedId == (tbSw != null ? tbSw.getId() : -2)) r = 13; + else r = -1; + try { + getSharedPreferences("app_prefs", MODE_PRIVATE).edit().putInt("renderer", r).apply(); + NativeApp.renderGpu(r); + } catch (Throwable ignored) {} + }); + } + if (btnGameState != null) { + btnGameState.setOnClickListener(v -> { + try { + SavesDialogFragment dialog = new SavesDialogFragment(); + dialog.show(getSupportFragmentManager(), "saves_dialog"); + } catch (Throwable ignored) {} + }); + } + } + } + } catch (Throwable ignored) {} + // Visibility toggle removed // PAD MaterialButton btn_pad_select = findViewById(R.id.btn_pad_select); @@ -711,7 +728,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF } } - private boolean allUIHidden = false; + // Visibility toggle removed; no global hidden state flag private void setControlsVisible(boolean visible) { int vis = visible ? View.VISIBLE : View.GONE; @@ -726,43 +743,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF if (llJoy != null) llJoy.setVisibility(vis); } - private void toggleAllUIVisibility() { - allUIHidden = !allUIHidden; - int vis = allUIHidden ? View.GONE : View.VISIBLE; - boolean hasController = isAnyControllerConnected(); - - // Hide/show all UI elements except the toggle button itself - View btnSettings = findViewById(R.id.btn_settings); - MaterialButton btnToggleControls = findViewById(R.id.btn_toggle_controls); - View btnFile = findViewById(R.id.btn_file); - View btnBios = findViewById(R.id.btn_bios); - View btnSaves = findViewById(R.id.btn_saves); - View llQuickActions = findViewById(R.id.ll_quick_actions); - - if (btnSettings != null) btnSettings.setVisibility(vis); - // Keep toggle button visible but change its icon - if (btnToggleControls != null) { - // Change icon based on visibility state - int iconRes = allUIHidden ? R.drawable.visibility_off_24px : R.drawable.visibility_24px; - btnToggleControls.setIcon(ContextCompat.getDrawable(this, iconRes)); - } - if (btnFile != null) btnFile.setVisibility(vis); - if (btnBios != null) btnBios.setVisibility(vis); - if (btnSaves != null) btnSaves.setVisibility(vis); - if (llQuickActions != null) llQuickActions.setVisibility(vis); - - // Hide/show on-screen controls - if (!allUIHidden) { - // When showing with a controller connected, keep touch controls hidden - setControlsVisible(!hasController); - } else { - // When hiding all UI, also hide controls - setControlsVisible(false); - } - - // Update renderer label when UI toggled back on - if (!allUIHidden) updateRendererButtonLabel(); - } + // Removed visibility toggle function // --- Controller presence handling --- private final InputManager.InputDeviceListener mInputDeviceListener = new InputManager.InputDeviceListener() { @@ -820,24 +801,11 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF int vis = hasController ? View.GONE : View.VISIBLE; View btnSettings = findViewById(R.id.btn_settings); - View btnFile = findViewById(R.id.btn_file); - View btnBios = findViewById(R.id.btn_bios); - View btnSaves = findViewById(R.id.btn_saves); - MaterialButton btnToggle = findViewById(R.id.btn_toggle_controls); View llQuickActions = findViewById(R.id.ll_quick_actions); - if (btnSettings != null) btnSettings.setVisibility(vis); - if (btnFile != null) btnFile.setVisibility(vis); - if (btnBios != null) btnBios.setVisibility(vis); - if (btnSaves != null) btnSaves.setVisibility(vis); + // Keep menu (settings) button visible even when a controller is connected + if (btnSettings != null) btnSettings.setVisibility(View.VISIBLE); if (llQuickActions != null) llQuickActions.setVisibility(vis); - if (btnToggle != null) btnToggle.setVisibility(View.VISIBLE); // always visible - // Set toggle icon and internal state - if (btnToggle != null) { - int iconRes = hasController ? R.drawable.visibility_off_24px : R.drawable.visibility_24px; - btnToggle.setIcon(ContextCompat.getDrawable(this, iconRes)); - } - allUIHidden = hasController; // Hide on-screen touch controls when a physical controller is connected setControlsVisible(!hasController); @@ -858,22 +826,10 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF private void updateRendererButtonLabel() { - MaterialButton btn_bios = findViewById(R.id.btn_bios); - if (btn_bios != null) { - int current = getCurrentRendererPref(); - try { - // Prefer runtime renderer from core to reflect per-game overrides - int runtime = NativeApp.getCurrentRenderer(); - // Only accept expected values - if (runtime == -1 || runtime == 12 || runtime == 13 || runtime == 14) { - current = runtime; - } - } catch (Throwable ignored) {} - btn_bios.setText(rendererShortLabel(current)); - } - } + // No-op: renderer label now handled in drawer +} - // Public minimal UI refresh hook for dialogs +// Public minimal UI refresh hook for dialogs public void refreshQuickUi() { updateRendererButtonLabel(); } @@ -1750,3 +1706,8 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF } } + + + + + diff --git a/app/src/main/java/com/izzy2lost/psx2/QuickActionsDialogFragment.java b/app/src/main/java/com/izzy2lost/psx2/QuickActionsDialogFragment.java index 5831c62..d896653 100644 --- a/app/src/main/java/com/izzy2lost/psx2/QuickActionsDialogFragment.java +++ b/app/src/main/java/com/izzy2lost/psx2/QuickActionsDialogFragment.java @@ -11,6 +11,9 @@ import androidx.fragment.app.DialogFragment; import com.google.android.material.button.MaterialButton; import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.materialswitch.MaterialSwitch; +import android.widget.Spinner; +import android.widget.ArrayAdapter; public class QuickActionsDialogFragment extends DialogFragment { @@ -19,15 +22,156 @@ public class QuickActionsDialogFragment extends DialogFragment { public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { View view = getLayoutInflater().inflate(R.layout.dialog_quick_actions, null, false); - MaterialButton btnOpenGames = view.findViewById(R.id.btn_open_games); - MaterialButton btnExitToMenu = view.findViewById(R.id.btn_exit_to_menu); - MaterialButton btnRestartGame = view.findViewById(R.id.btn_restart_game); - MaterialButton btnQuitApp = view.findViewById(R.id.btn_quit_app); + MaterialButton btnPower = view.findViewById(R.id.btn_quick_power); + MaterialButton btnReboot = view.findViewById(R.id.btn_quick_reboot); + com.google.android.material.button.MaterialButtonToggleGroup tgRenderer = view.findViewById(R.id.qa_tg_renderer); + View tbAt = view.findViewById(R.id.qa_tb_at); + View tbVk = view.findViewById(R.id.qa_tb_vk); + View tbGl = view.findViewById(R.id.qa_tb_gl); + View tbSw = view.findViewById(R.id.qa_tb_sw); + MaterialButton btnGames = view.findViewById(R.id.btn_quick_games); + MaterialButton btnSaves = view.findViewById(R.id.btn_quick_saves); MaterialButton btnCancel = view.findViewById(R.id.btn_cancel); + Spinner spAspect = view.findViewById(R.id.qa_sp_aspect_ratio); + Spinner spBlending = view.findViewById(R.id.qa_sp_blending_accuracy); + MaterialSwitch swWide = view.findViewById(R.id.qa_sw_widescreen); + MaterialSwitch swNoInt = view.findViewById(R.id.qa_sw_no_interlacing); + MaterialSwitch swLoadTex = view.findViewById(R.id.qa_sw_load_textures); + MaterialSwitch swAsyncTex = view.findViewById(R.id.qa_sw_async_textures); + MaterialSwitch swPrecache = view.findViewById(R.id.qa_sw_precache_textures); - // Open Games/Covers dialog - if (btnOpenGames != null) { - btnOpenGames.setOnClickListener(v -> { + // Power (Quit App) + if (btnPower != null) { + btnPower.setOnClickListener(v -> new MaterialAlertDialogBuilder(requireContext(), + com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog) + .setCustomTitle(UiUtils.centeredDialogTitle(requireContext(), "Power Off")) + .setMessage("Quit PSX2?") + .setNegativeButton("Cancel", null) + .setPositiveButton("Quit", (d, w) -> { quitApp(); dismissAllowingStateLoss(); }) + .show()); + } + + // Reboot game + if (btnReboot != null) { + btnReboot.setOnClickListener(v -> new MaterialAlertDialogBuilder(requireContext(), + com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog) + .setCustomTitle(UiUtils.centeredDialogTitle(requireContext(), "Reboot")) + .setMessage("Restart the current game?") + .setNegativeButton("Cancel", null) + .setPositiveButton("Reboot", (d, w) -> { + if (requireActivity() instanceof MainActivity) { + ((MainActivity) requireActivity()).rebootEmu(); + } else { NativeApp.shutdown(); } + dismissAllowingStateLoss(); + }) + .show()); + } + + // Renderer toggle: set current and handle changes + if (tgRenderer != null) { + int current = -1; + try { current = NativeApp.getCurrentRenderer(); } catch (Throwable ignored) {} + if (current == 14 && tbVk != null) tgRenderer.check(tbVk.getId()); + else if (current == 12 && tbGl != null) tgRenderer.check(tbGl.getId()); + else if (current == 13 && tbSw != null) tgRenderer.check(tbSw.getId()); + else if (tbAt != null) tgRenderer.check(tbAt.getId()); + + tgRenderer.addOnButtonCheckedListener((group, checkedId, isChecked) -> { + if (!isChecked) return; + int r = -1; + if (checkedId == (tbVk != null ? tbVk.getId() : -2)) r = 14; + else if (checkedId == (tbGl != null ? tbGl.getId() : -2)) r = 12; + else if (checkedId == (tbSw != null ? tbSw.getId() : -2)) r = 13; + else r = -1; + try { + requireContext().getSharedPreferences("app_prefs", android.content.Context.MODE_PRIVATE) + .edit().putInt("renderer", r).apply(); + NativeApp.renderGpu(r); + } catch (Throwable ignored) {} + }); + } + + // Aspect Ratio spinner + if (spAspect != null) { + ArrayAdapter aspectAdapter = ArrayAdapter.createFromResource(requireContext(), + R.array.aspect_ratio_entries, android.R.layout.simple_spinner_item); + aspectAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spAspect.setAdapter(aspectAdapter); + int savedAspect = requireContext().getSharedPreferences("app_prefs", android.content.Context.MODE_PRIVATE) + .getInt("aspect_ratio", 1); + if (savedAspect < 0 || savedAspect >= aspectAdapter.getCount()) savedAspect = 1; + spAspect.setSelection(savedAspect); + spAspect.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() { + @Override public void onItemSelected(android.widget.AdapterView parent, View view1, int position, long id) { + requireContext().getSharedPreferences("app_prefs", android.content.Context.MODE_PRIVATE) + .edit().putInt("aspect_ratio", position).apply(); + try { NativeApp.setAspectRatio(position); } catch (Throwable ignored) {} + } + @Override public void onNothingSelected(android.widget.AdapterView parent) {} + }); + } + + // Blending Accuracy spinner + if (spBlending != null) { + ArrayAdapter blendAdapter = ArrayAdapter.createFromResource(requireContext(), + R.array.blending_accuracy_entries, android.R.layout.simple_spinner_item); + blendAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spBlending.setAdapter(blendAdapter); + int savedBlend = requireContext().getSharedPreferences("app_prefs", android.content.Context.MODE_PRIVATE) + .getInt("blending_accuracy", 1); + if (savedBlend < 0 || savedBlend >= blendAdapter.getCount()) savedBlend = 1; + spBlending.setSelection(savedBlend); + spBlending.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() { + @Override public void onItemSelected(android.widget.AdapterView parent, View view12, int position, long id) { + requireContext().getSharedPreferences("app_prefs", android.content.Context.MODE_PRIVATE) + .edit().putInt("blending_accuracy", position).apply(); + try { NativeApp.setBlendingAccuracy(position); } catch (Throwable ignored) {} + } + @Override public void onNothingSelected(android.widget.AdapterView parent) {} + }); + } + + // Switches: widescreen, no interlacing, textures + final android.content.SharedPreferences prefs = requireContext().getSharedPreferences("app_prefs", android.content.Context.MODE_PRIVATE); + if (swWide != null) { + swWide.setChecked(prefs.getBoolean("widescreen_patches", true)); + swWide.setOnCheckedChangeListener((buttonView, isChecked) -> { + prefs.edit().putBoolean("widescreen_patches", isChecked).apply(); + try { NativeApp.setWidescreenPatches(isChecked); } catch (Throwable ignored) {} + }); + } + if (swNoInt != null) { + swNoInt.setChecked(prefs.getBoolean("no_interlacing_patches", true)); + swNoInt.setOnCheckedChangeListener((buttonView, isChecked) -> { + prefs.edit().putBoolean("no_interlacing_patches", isChecked).apply(); + try { NativeApp.setNoInterlacingPatches(isChecked); } catch (Throwable ignored) {} + }); + } + if (swLoadTex != null) { + swLoadTex.setChecked(prefs.getBoolean("load_textures", false)); + swLoadTex.setOnCheckedChangeListener((buttonView, isChecked) -> { + prefs.edit().putBoolean("load_textures", isChecked).apply(); + try { NativeApp.setLoadTextures(isChecked); } catch (Throwable ignored) {} + }); + } + if (swAsyncTex != null) { + swAsyncTex.setChecked(prefs.getBoolean("async_texture_loading", true)); + swAsyncTex.setOnCheckedChangeListener((buttonView, isChecked) -> { + prefs.edit().putBoolean("async_texture_loading", isChecked).apply(); + try { NativeApp.setAsyncTextureLoading(isChecked); } catch (Throwable ignored) {} + }); + } + if (swPrecache != null) { + swPrecache.setChecked(prefs.getBoolean("precache_textures", false)); + swPrecache.setOnCheckedChangeListener((buttonView, isChecked) -> { + prefs.edit().putBoolean("precache_textures", isChecked).apply(); + try { NativeApp.setPrecacheTextureReplacements(isChecked); } catch (Throwable ignored) {} + }); + } + + // Games: open covers dialog + if (btnGames != null) { + btnGames.setOnClickListener(v -> { if (requireActivity() instanceof MainActivity) { ((MainActivity) requireActivity()).openGamesDialog(); } @@ -35,62 +179,16 @@ public class QuickActionsDialogFragment extends DialogFragment { }); } - // Exit to Menu - not implemented yet, show placeholder - if (btnExitToMenu != null) { - btnExitToMenu.setOnClickListener(v -> { - new MaterialAlertDialogBuilder(requireContext(), - com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog) - .setCustomTitle(UiUtils.centeredDialogTitle(requireContext(), "Exit to Menu")) - .setMessage("This feature is not implemented yet. Would you like to quit the app instead?") - .setNegativeButton("Cancel", null) - .setPositiveButton("Quit App", (d, w) -> { - quitApp(); - dismissAllowingStateLoss(); - }) - .show(); - }); - } - - // Restart Game - use existing reboot functionality - if (btnRestartGame != null) { - btnRestartGame.setOnClickListener(v -> { - new MaterialAlertDialogBuilder(requireContext(), - com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog) - .setCustomTitle(UiUtils.centeredDialogTitle(requireContext(), "Restart Game")) - .setMessage("Restart the current game?") - .setNegativeButton("Cancel", null) - .setPositiveButton("Restart", (d, w) -> { - if (requireActivity() instanceof MainActivity) { - ((MainActivity) requireActivity()).rebootEmu(); - } else { - NativeApp.shutdown(); - } - dismissAllowingStateLoss(); - }) - .show(); - }); - } - - // Quit App - use existing power functionality - if (btnQuitApp != null) { - btnQuitApp.setOnClickListener(v -> { - new MaterialAlertDialogBuilder(requireContext(), - com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog) - .setCustomTitle(UiUtils.centeredDialogTitle(requireContext(), "Quit App")) - .setMessage("Quit PSX2?") - .setNegativeButton("Cancel", null) - .setPositiveButton("Quit", (d, w) -> { - quitApp(); - dismissAllowingStateLoss(); - }) - .show(); + // Saves: open save states dialog + if (btnSaves != null) { + btnSaves.setOnClickListener(v -> { + try { new SavesDialogFragment().show(getParentFragmentManager(), "saves_dialog"); } catch (Throwable ignored) {} + dismissAllowingStateLoss(); }); } // Cancel button - if (btnCancel != null) { - btnCancel.setOnClickListener(v -> dismissAllowingStateLoss()); - } + if (btnCancel != null) btnCancel.setOnClickListener(v -> dismissAllowingStateLoss()); return new MaterialAlertDialogBuilder(requireContext(), com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog) diff --git a/app/src/main/java/com/izzy2lost/psx2/SetupWizardDialogFragment.java b/app/src/main/java/com/izzy2lost/psx2/SetupWizardDialogFragment.java index 6ef752d..2482beb 100644 --- a/app/src/main/java/com/izzy2lost/psx2/SetupWizardDialogFragment.java +++ b/app/src/main/java/com/izzy2lost/psx2/SetupWizardDialogFragment.java @@ -118,8 +118,19 @@ public class SetupWizardDialogFragment extends DialogFragment { if (isDataFolderPicked() && isGamesFolderPicked() && isBiosPresent()) { requireContext().getSharedPreferences("app_prefs", android.content.Context.MODE_PRIVATE) .edit().putBoolean("first_run_done", true).apply(); - try { ((MainActivity) requireActivity()).setSetupWizardActive(false); } catch (Throwable ignored) {} + MainActivity a = null; + try { a = (MainActivity) requireActivity(); a.setSetupWizardActive(false); } catch (Throwable ignored) {} dismissAllowingStateLoss(); + if (a != null) { + // Open the games dialog just like the old GAMES button + final MainActivity act = a; + View decor = act.getWindow() != null ? act.getWindow().getDecorView() : null; + if (decor != null) { + decor.postDelayed(act::openGamesDialog, 150); + } else { + act.runOnUiThread(act::openGamesDialog); + } + } } }); LinearLayout.LayoutParams lp4 = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); diff --git a/app/src/main/res/drawable/menu_24px.xml b/app/src/main/res/drawable/menu_24px.xml new file mode 100644 index 0000000..959a39e --- /dev/null +++ b/app/src/main/res/drawable/menu_24px.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/drawable/save_24px.xml b/app/src/main/res/drawable/save_24px.xml new file mode 100644 index 0000000..b5d0e0b --- /dev/null +++ b/app/src/main/res/drawable/save_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/stadia_controller_24px.xml b/app/src/main/res/drawable/stadia_controller_24px.xml new file mode 100644 index 0000000..d2d705b --- /dev/null +++ b/app/src/main/res/drawable/stadia_controller_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml index 6cfb77f..533ec6f 100644 --- a/app/src/main/res/layout-land/activity_main.xml +++ b/app/src/main/res/layout-land/activity_main.xml @@ -1,18 +1,23 @@ - + android:layout_height="match_parent"> + + - + - - + - + - - - + @@ -445,5 +403,23 @@ android:layout_column="2" /> + - + + + + + + + diff --git a/app/src/main/res/layout-land/dialog_covers_grid.xml b/app/src/main/res/layout-land/dialog_covers_grid.xml index aa7caea..0a622e3 100644 --- a/app/src/main/res/layout-land/dialog_covers_grid.xml +++ b/app/src/main/res/layout-land/dialog_covers_grid.xml @@ -1,10 +1,15 @@ - + android:layout_height="match_parent"> + + - + + + + + + diff --git a/app/src/main/res/layout-port/activity_main.xml b/app/src/main/res/layout-port/activity_main.xml index 81eacf7..4aa38e3 100644 --- a/app/src/main/res/layout-port/activity_main.xml +++ b/app/src/main/res/layout-port/activity_main.xml @@ -1,18 +1,23 @@ - + android:layout_height="match_parent"> + + - + - - + - + - - - + @@ -438,5 +396,23 @@ android:layout_column="2" /> + - + + + + + + + diff --git a/app/src/main/res/layout-port/dialog_covers_grid.xml b/app/src/main/res/layout-port/dialog_covers_grid.xml index 59d3dab..4dd0d3c 100644 --- a/app/src/main/res/layout-port/dialog_covers_grid.xml +++ b/app/src/main/res/layout-port/dialog_covers_grid.xml @@ -1,10 +1,15 @@ - + android:layout_height="match_parent"> + + - + + + + + + diff --git a/app/src/main/res/layout/dialog_quick_actions.xml b/app/src/main/res/layout/dialog_quick_actions.xml index 37e6c14..f05fc2b 100644 --- a/app/src/main/res/layout/dialog_quick_actions.xml +++ b/app/src/main/res/layout/dialog_quick_actions.xml @@ -1,5 +1,6 @@ - + + android:orientation="horizontal" + android:layout_marginBottom="8dp"> - + + + + + + + + + app:singleSelection="true" + app:selectionRequired="true"> - + + + + + + + + + + + android:orientation="horizontal" + android:layout_marginBottom="16dp"> - + + + + + + + + + + + android:layout_marginBottom="8dp"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -82,6 +85,7 @@ android:id="@+id/rb_renderer_sw" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:minWidth="0dp" android:text="SW" android:layout_marginStart="16dp"/> diff --git a/app/src/main/res/layout/drawer_header_settings.xml b/app/src/main/res/layout/drawer_header_settings.xml new file mode 100644 index 0000000..a6a2904 --- /dev/null +++ b/app/src/main/res/layout/drawer_header_settings.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build.gradle b/build.gradle index 9c6650e..06e590f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,4 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.7.3' apply false -} \ No newline at end of file + id 'com.android.application' version '8.11.1' apply false +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index dedd5d1..d4081da 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME