From 8a73b8baf5da7260523e689a5304c898923d0f20 Mon Sep 17 00:00:00 2001 From: Lynxifyy <297587416+Lynxifyy@users.noreply.github.com> Date: Sat, 14 Nov 2026 20:59:00 +0000 Subject: [PATCH] chore(ui): remove the legacy launcher UI for a clean skeleton The launcher's visual UI is deleted so a new UI can be dropped in; every system it sat on is kept. Nothing here changes a mod, engine or settings behaviour - only the chrome that drew them. Launcher UI - delete the 12 screens, the widget kit, the nav/rail shell, the tabs and the splash art; 94 layouts -> 20 (the kept 20 are the in-game HUD/game host overlays that ARE the mod systems) - keep 7 placeholder Activities (BaseActivity + Main/Splash/Library/ Settings/ModsFullscreen/Crash) as manifest/Intent targets; they draw nothing - delete the dead legacy drawables (splash_bg_*, launch_bg_*, dock/nav) and the dead Widget.Caramel.NavTab/NavRailTab/QuickDock styles - keep the reusable token/style system (UiTokens, Widget.Caramel.Card*, TextAppearance.Caramel.*, GlowDimens, generic bg_*/ic_*) In-game Mod Menu UI - bump the sweetloader submodule to chore/remove-in-game-menu, which drops the native Dear ImGui menu (src/pl/menu/*, third_party/imgui/) - drop the Java bridge wiring from MinecraftActivity and the overlay manager; the mod systems themselves are untouched Tests - trim the two tests that pinned the deleted button drawables (ButtonPressDrawableTest, RadiusScaleTest) - refresh the findViewById hygiene baseline (32 -> 29) --- .openhands/memory/2026-11-14.md | 83 + .openhands/memory/MEMORY.md | 22 + app/src/main/AndroidManifest.xml | 167 -- app/src/main/cpp/sweetloader | 2 +- .../controller/ControllerToastView.java | 2 +- .../core/minecraft/MinecraftActivity.kt | 18 - .../mods/inbuilt/InbuiltModuleProvider.java | 3 +- .../inbuilt/manager/InbuiltModManager.java | 47 - .../mods/inbuilt/overlay/CapePreviewView.java | 3 - .../mods/inbuilt/overlay/CosmeticsPanel.java | 1100 -------- .../inbuilt/overlay/CursorController.java | 167 -- .../mods/inbuilt/overlay/CursorRouter.java | 79 - .../inbuilt/overlay/InGameMenuController.java | 402 --- .../overlay/InbuiltOverlayManager.java | 78 +- .../inbuilt/overlay/MenuCursorOverlay.java | 161 -- .../mods/inbuilt/overlay/ModMenuButton.java | 304 -- .../mods/inbuilt/overlay/ModMenuMetrics.java | 64 - .../mods/inbuilt/overlay/ModMenuPalette.java | 287 -- .../mods/inbuilt/overlay/ModMenuSettings.java | 276 -- .../mods/inbuilt/overlay/ModMenuTheme.java | 246 -- .../inbuilt/overlay/MoreButtonsEditor.java | 854 ------ .../inbuilt/overlay/PauseMenuActions.java | 92 - .../mods/inbuilt/overlay/PauseMenuRow.java | 286 -- .../mods/inbuilt/overlay/QuickSettings.java | 126 - .../inbuilt/overlay/QuickSettingsApplier.java | 152 - .../inbuilt/overlay/QuickSettingsPanel.java | 232 -- .../core/mods/inbuilt/overlay/VoicePanel.java | 587 ---- .../core/mods/inbuilt/overlay/VoiceUiKit.java | 307 -- .../launcher/preloader/PreloaderMenu.java | 142 - .../launcher/ui/activities/AboutActivity.java | 235 -- .../ui/activities/AccountSwitchPopup.java | 353 --- .../ui/activities/AccountsActivity.java | 289 -- .../launcher/ui/activities/BaseActivity.java | 1625 +---------- .../ui/activities/ChangelogActivity.java | 323 --- .../ui/activities/ContentDetailsActivity.java | 501 ---- .../ui/activities/ContentListActivity.java | 1326 --------- .../activities/ContentManagementActivity.java | 368 --- .../launcher/ui/activities/CrashActivity.java | 20 + .../launcher/ui/activities/CrashActivity.kt | 320 --- .../ui/activities/CurseForgeActivity.java | 314 --- .../activities/CustomFlatWorldActivity.java | 309 -- .../ui/activities/CustomizeActivity.java | 271 -- .../ui/activities/DiagnosticsActivity.java | 932 +----- .../activities/InstanceSettingsActivity.java | 739 ----- .../launcher/ui/activities/IntentHandler.java | 5 +- .../ui/activities/LibraryActivity.java | 189 +- .../launcher/ui/activities/MainActivity.java | 2242 +-------------- .../ui/activities/ModConfigActivity.java | 231 -- .../ui/activities/ModDetailActivity.java | 350 --- .../ui/activities/ModHubActivity.java | 577 ---- .../ui/activities/ModrinthDetailActivity.java | 343 --- .../ui/activities/ModsFullscreenActivity.java | 474 +--- .../ui/activities/MsftLoginActivity.java | 861 ------ .../ui/activities/OptionsEditorActivity.java | 253 -- .../ui/activities/QuickLaunchActivity.java | 434 --- .../ui/activities/SettingsActivity.java | 1838 +----------- .../ui/activities/SetupWizardActivity.java | 671 ----- .../ui/activities/SplashActivity.java | 415 +-- .../ui/activities/UiGalleryActivity.java | 529 ---- .../ui/activities/VoiceChatActivity.java | 787 ------ .../ui/activities/WorldEditorActivity.java | 270 -- .../launcher/ui/adapter/AccountsAdapter.java | 133 - .../ui/adapter/CurseForgeContentAdapter.java | 227 -- .../launcher/ui/adapter/DownloadsAdapter.java | 136 - .../launcher/ui/adapter/FileListAdapter.java | 108 - .../ui/adapter/InstallationAdapter.java | 243 -- .../launcher/ui/adapter/ModConfigAdapter.java | 686 ----- .../ui/adapter/ModrinthContentAdapter.java | 219 -- .../ui/adapter/ModrinthVersionAdapter.java | 140 - .../launcher/ui/adapter/ModsAdapter.java | 306 -- .../ui/adapter/OptionsPropertiesAdapter.java | 181 -- .../ui/adapter/QuickLaunchAdapter.java | 112 - .../ui/adapter/ResourcePacksAdapter.java | 158 -- .../ui/adapter/ScreenshotsAdapter.java | 169 -- .../launcher/ui/adapter/ServersAdapter.java | 233 -- .../launcher/ui/adapter/SkinsAdapter.java | 225 -- .../ui/adapter/StructuresAdapter.java | 85 - .../ui/adapter/WorldPropertiesAdapter.java | 216 -- .../launcher/ui/adapter/WorldsAdapter.java | 147 - .../ui/animation/OverscrollRefreshLayout.java | 141 - .../launcher/ui/dialogs/ProfileSheet.java | 245 -- .../fragments/ControllerSettingsFragment.java | 1389 --------- .../ui/fragments/SkinsSettingsFragment.java | 557 ---- .../ui/splash/SplashBackgroundRegistry.java | 95 - .../launcher/ui/splash/SplashBackgrounds.java | 70 - .../client/launcher/ui/splash/SplashInit.java | 97 - .../launcher/ui/splash/SplashSpinner.java | 119 - .../launcher/ui/splash/SplashTimeline.java | 80 - .../launcher/ui/util/SceneTransitions.java | 119 - .../launcher/ui/views/Controller3DView.java | 415 --- .../ui/views/ControllerIllustrationView.java | 931 ------ .../launcher/ui/views/CurvePreviewView.java | 167 -- .../ui/views/LauncherCursorInput.java | 57 - .../ui/views/LauncherCursorOverlay.java | 197 -- .../launcher/ui/views/StickPreviewView.java | 127 - .../launcher/util/PersonalizationManager.java | 14 - .../client/ui/animation/LaunchIntro.java | 96 - .../client/ui/components/PodEntrance.java | 63 - .../client/ui/components/UiAccessibility.java | 217 -- .../client/ui/components/UiButton.java | 114 - .../client/ui/components/UiCard.java | 21 - .../client/ui/components/UiChip.java | 80 - .../client/ui/components/UiGlass.java | 153 - .../client/ui/components/UiRailButton.java | 56 - .../chimeramc/client/ui/components/UiRow.java | 70 - .../client/ui/components/UiSearchField.java | 75 - .../client/ui/components/UiSegmented.java | 56 - .../client/ui/components/UiSlider.java | 39 - .../client/ui/components/UiToggle.java | 62 - .../client/ui/components/UiWindow.java | 119 - .../ui/compose/account/AccountSection.kt | 227 -- .../ui/compose/account/AccountSectionHost.kt | 37 - .../compose/account/AccountSectionState.java | 110 - .../ui/compose/caramel/CaramelControls.kt | 328 --- .../client/ui/compose/caramel/CaramelGlass.kt | 163 -- .../ui/compose/caramel/CaramelLabels.kt | 176 -- .../client/ui/compose/caramel/CaramelTheme.kt | 127 - .../client/ui/compose/caramel/CaramelUtil.kt | 7 - .../caramel/launch/CaramelLaunchHost.kt | 86 - .../caramel/launch/CaramelLaunchScreen.kt | 631 ----- .../caramel/launch/CaramelLaunchState.kt | 50 - .../ui/compose/connect/ConnectionStatus.kt | 168 -- .../compose/connect/ConnectionStatusHost.kt | 131 - .../ui/compose/launch/LaunchHeroState.java | 72 - .../ui/compose/launch/PreviewBackground.java | 81 - .../ui/fragments/InstallationsFragment.java | 627 ----- .../ui/fragments/InstancesFragment.java | 1273 --------- .../client/ui/fragments/ReplayFragment.java | 98 - .../client/ui/navigation/LauncherTab.java | 196 -- .../client/ui/navigation/NavAccent.java | 58 - .../ui/navigation/NavBoundsOverlay.java | 65 - .../client/ui/navigation/NavDebugBounds.java | 91 - .../client/ui/navigation/NavLayoutMode.java | 84 - .../client/ui/navigation/NavRailPolicy.java | 55 - .../client/ui/navigation/NavTabBadge.java | 73 - .../client/ui/navigation/NavTabLongPress.java | 66 - .../main/res/drawable-nodpi/launch_bg_1.jpg | Bin 4565 -> 0 bytes .../main/res/drawable-nodpi/launch_bg_2.jpg | Bin 8045 -> 0 bytes .../main/res/drawable-nodpi/launch_bg_3.jpg | Bin 4292 -> 0 bytes .../main/res/drawable-nodpi/splash_bg_1.jpg | Bin 111882 -> 0 bytes .../main/res/drawable-nodpi/splash_bg_2.jpg | Bin 145790 -> 0 bytes .../main/res/drawable-nodpi/splash_bg_3.jpg | Bin 349954 -> 0 bytes .../main/res/drawable-nodpi/splash_bg_4.jpg | Bin 272436 -> 0 bytes .../main/res/drawable-nodpi/splash_bg_5.jpg | Bin 145526 -> 0 bytes .../main/res/drawable-nodpi/splash_bg_6.png | Bin 295539 -> 0 bytes .../main/res/drawable-nodpi/splash_bg_7.png | Bin 356869 -> 0 bytes .../main/res/drawable-nodpi/splash_bg_8.png | Bin 226461 -> 0 bytes app/src/main/res/drawable/bg_dock_play.xml | 90 - .../res/drawable/bg_dock_version_chip.xml | 45 - .../main/res/drawable/bg_launch_button.xml | 6 - app/src/main/res/drawable/bg_launch_dock.xml | 14 - .../main/res/drawable/bg_launch_quickdock.xml | 9 - app/src/main/res/drawable/bg_logo_glow.xml | 14 - app/src/main/res/drawable/bg_mod_menu_fab.xml | 12 - .../main/res/drawable/bg_mod_menu_toggle.xml | 9 - app/src/main/res/drawable/bg_split_launch.xml | 80 - .../main/res/drawable/bg_vip_icon_button.xml | 17 - app/src/main/res/drawable/bg_vip_surface.xml | 7 - .../main/res/drawable/ic_nav_instances.xml | 19 - app/src/main/res/drawable/ic_nav_launch.xml | 10 - app/src/main/res/drawable/ic_nav_settings.xml | 21 - .../res/drawable/nav_active_indicator.xml | 10 - .../main/res/drawable/nav_rail_background.xml | 22 - app/src/main/res/drawable/nav_tab_dot.xml | 16 - app/src/main/res/drawable/splash_scrim.xml | 11 - app/src/main/res/layout/activity_about.xml | 468 --- app/src/main/res/layout/activity_accounts.xml | 216 -- .../main/res/layout/activity_changelog.xml | 47 - .../res/layout/activity_content_details.xml | 154 - .../main/res/layout/activity_content_list.xml | 146 - .../layout/activity_content_management.xml | 321 --- .../main/res/layout/activity_controller.xml | 465 --- app/src/main/res/layout/activity_crash.xml | 164 -- .../main/res/layout/activity_curseforge.xml | 119 - .../res/layout/activity_custom_flat_world.xml | 186 -- .../main/res/layout/activity_customize.xml | 101 - .../main/res/layout/activity_diagnostics.xml | 393 --- .../res/layout/activity_installations.xml | 298 -- .../res/layout/activity_instance_settings.xml | 806 ------ .../main/res/layout/activity_instances.xml | 298 -- app/src/main/res/layout/activity_library.xml | 113 - app/src/main/res/layout/activity_main.xml | 24 - .../main/res/layout/activity_mod_config.xml | 95 - .../main/res/layout/activity_mod_detail.xml | 253 -- app/src/main/res/layout/activity_mod_hub.xml | 272 -- .../res/layout/activity_modrinth_detail.xml | 103 - .../res/layout/activity_mods_fullscreen.xml | 295 -- .../main/res/layout/activity_msft_login.xml | 833 ------ .../res/layout/activity_options_editor.xml | 96 - .../main/res/layout/activity_quick_launch.xml | 43 - app/src/main/res/layout/activity_settings.xml | 2502 ----------------- .../main/res/layout/activity_setup_wizard.xml | 327 --- app/src/main/res/layout/activity_skins.xml | 187 -- app/src/main/res/layout/activity_splash.xml | 124 - .../main/res/layout/activity_voice_chat.xml | 570 ---- .../main/res/layout/activity_world_editor.xml | 98 - app/src/main/res/layout/dialog_add_server.xml | 55 - .../main/res/layout/dialog_connect_server.xml | 39 - app/src/main/res/layout/dialog_custom_uri.xml | 31 - app/src/main/res/layout/dialog_file_list.xml | 29 - app/src/main/res/layout/dialog_load_world.xml | 23 - .../res/layout/dialog_marketplace_pass.xml | 22 - .../main/res/layout/dialog_realm_invite.xml | 23 - .../main/res/layout/dialog_skin_import.xml | 93 - .../main/res/layout/dialog_slash_command.xml | 31 - .../main/res/layout/dialog_structure_list.xml | 23 - .../res/layout/dialog_transfer_content.xml | 64 - app/src/main/res/layout/fragment_replay.xml | 9 - .../res/layout/item_curseforge_content.xml | 86 - .../res/layout/item_curseforge_footer.xml | 60 - .../main/res/layout/item_download_file.xml | 69 - app/src/main/res/layout/item_file_detail.xml | 60 - app/src/main/res/layout/item_flat_layer.xml | 65 - .../res/layout/item_installation_version.xml | 105 - .../main/res/layout/item_instance_card.xml | 131 - app/src/main/res/layout/item_mod.xml | 146 - .../main/res/layout/item_mod_config_node.xml | 58 - .../res/layout/item_mod_detail_meta_row.xml | 26 - .../main/res/layout/item_modrinth_version.xml | 63 - app/src/main/res/layout/item_msft_account.xml | 97 - .../main/res/layout/item_option_property.xml | 48 - .../main/res/layout/item_property_header.xml | 19 - app/src/main/res/layout/item_quick_launch.xml | 35 - .../main/res/layout/item_resource_pack.xml | 104 - app/src/main/res/layout/item_screenshot.xml | 108 - app/src/main/res/layout/item_server.xml | 82 - app/src/main/res/layout/item_skin_pack.xml | 103 - app/src/main/res/layout/item_structure.xml | 44 - .../main/res/layout/item_voice_channel.xml | 33 - app/src/main/res/layout/item_world.xml | 170 -- .../main/res/layout/item_world_property.xml | 69 - app/src/main/res/layout/nav_bar.xml | 251 -- app/src/main/res/layout/nav_rail.xml | 222 -- .../res/layout/overlay_mod_menu_button.xml | 21 - .../res/layout/overlay_pause_menu_row.xml | 27 - .../main/res/layout/popup_account_switch.xml | 90 - .../main/res/layout/spinner_dropdown_item.xml | 12 - app/src/main/res/layout/spinner_font_item.xml | 12 - app/src/main/res/layout/spinner_item.xml | 10 - app/src/main/res/values/styles.xml | 85 - .../client/core/auth/MsftLoginFlowTest.java | 72 - .../inbuilt/overlay/CursorControllerTest.java | 210 -- .../inbuilt/overlay/CursorRouterTest.java | 89 - .../inbuilt/overlay/ModMenuMetricsTest.java | 52 - .../inbuilt/overlay/ModMenuPaletteTest.java | 162 -- .../inbuilt/overlay/ModMenuSettingsTest.java | 207 -- .../inbuilt/overlay/PauseMenuActionsTest.java | 69 - .../inbuilt/overlay/QuickSettingsTest.java | 81 - .../core/setup/SetupWizardShowTest.java | 61 - .../voice/VoiceRelaySettingsRemovedTest.java | 74 - .../inbuilt/overlay/ModMenuNavLayoutTest.java | 65 - .../ModUnavailableReasonResourcesTest.java | 85 - .../ui/activities/LaunchDockStyleTest.java | 65 - .../ui/activities/LaunchTabResourcesTest.java | 92 - .../ui/activities/SettingsBindingTest.java | 68 - .../ui/splash/SplashBackgroundsTest.java | 114 - .../ui/views/LaunchRingPaletteTest.java | 83 - .../activities/SetupWizardLastTabsTest.java | 91 - .../ui/activities/SetupWizardPopupTest.java | 114 - .../activities/SetupWizardResourcesTest.java | 75 - .../ui/activities/SetupWizardStateTest.java | 59 - .../client/ui/animation/LaunchIntroTest.java | 68 - .../components/ButtonPressDrawableTest.java | 23 - .../client/ui/components/PodEntranceTest.java | 68 - .../client/ui/components/RadiusScaleTest.java | 4 +- .../components/SkeletonPlaceholderTest.java | 94 - .../ui/components/UiAccessibilityTest.java | 109 - .../ui/components/UiButtonPressTest.java | 79 - .../client/ui/components/UiGlassTest.java | 94 - .../client/ui/components/UiTokensTest.java | 124 - .../account/AccountSectionStateTest.java | 81 - .../caramel/CaramelPaletteParityTest.java | 127 - .../compose/connect/ConnectionStatusTest.kt | 207 -- .../compose/launch/LaunchHeroStateTest.java | 43 - .../compose/launch/LaunchTabLayoutTest.java | 73 - .../compose/launch/PreviewBackgroundTest.java | 65 - .../theme/ComposeGlassSurfaceTest.java | 104 - .../client/ui/navigation/LauncherTabTest.java | 171 -- .../client/ui/navigation/NavAccentTest.java | 60 - .../ui/navigation/NavDebugBoundsGateTest.java | 60 - .../ui/navigation/NavDebugBoundsTest.java | 94 - .../ui/navigation/NavFocusOrderTest.java | 132 - .../ui/navigation/NavLayoutModeTest.java | 128 - .../ui/navigation/NavRailPolicyTest.java | 44 - .../ui/navigation/NavShellResourcesTest.java | 275 -- .../client/ui/navigation/NavTabBadgeTest.java | 59 - .../ui/navigation/NavTabLongPressTest.java | 82 - .../ui/views/LauncherCursorInputTest.java | 64 - .../chimeramc/client/util/EmptyCatchTest.java | 3 +- scripts/hygiene/findviewbyid-baseline.txt | 45 +- 290 files changed, 211 insertions(+), 57367 deletions(-) create mode 100644 .openhands/memory/2026-11-14.md delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/core/mods/inbuilt/overlay/CosmeticsPanel.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/core/mods/inbuilt/overlay/CursorController.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/core/mods/inbuilt/overlay/CursorRouter.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/core/mods/inbuilt/overlay/InGameMenuController.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/core/mods/inbuilt/overlay/MenuCursorOverlay.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/core/mods/inbuilt/overlay/ModMenuButton.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/core/mods/inbuilt/overlay/ModMenuMetrics.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/core/mods/inbuilt/overlay/ModMenuPalette.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/core/mods/inbuilt/overlay/ModMenuSettings.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/core/mods/inbuilt/overlay/ModMenuTheme.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/core/mods/inbuilt/overlay/MoreButtonsEditor.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/core/mods/inbuilt/overlay/PauseMenuActions.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/core/mods/inbuilt/overlay/PauseMenuRow.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/core/mods/inbuilt/overlay/QuickSettings.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/core/mods/inbuilt/overlay/QuickSettingsApplier.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/core/mods/inbuilt/overlay/QuickSettingsPanel.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/core/mods/inbuilt/overlay/VoicePanel.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/core/mods/inbuilt/overlay/VoiceUiKit.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/preloader/PreloaderMenu.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/AboutActivity.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/AccountSwitchPopup.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/AccountsActivity.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/ChangelogActivity.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/ContentDetailsActivity.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/ContentListActivity.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/ContentManagementActivity.java create mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/CrashActivity.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/CrashActivity.kt delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/CurseForgeActivity.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/CustomFlatWorldActivity.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/CustomizeActivity.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/InstanceSettingsActivity.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/ModConfigActivity.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/ModDetailActivity.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/ModHubActivity.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/ModrinthDetailActivity.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/MsftLoginActivity.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/OptionsEditorActivity.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/QuickLaunchActivity.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/SetupWizardActivity.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/UiGalleryActivity.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/VoiceChatActivity.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/activities/WorldEditorActivity.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/adapter/AccountsAdapter.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/adapter/CurseForgeContentAdapter.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/adapter/DownloadsAdapter.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/adapter/FileListAdapter.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/adapter/InstallationAdapter.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/adapter/ModConfigAdapter.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/adapter/ModrinthContentAdapter.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/adapter/ModrinthVersionAdapter.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/adapter/ModsAdapter.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/adapter/OptionsPropertiesAdapter.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/adapter/QuickLaunchAdapter.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/adapter/ResourcePacksAdapter.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/adapter/ScreenshotsAdapter.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/adapter/ServersAdapter.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/adapter/SkinsAdapter.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/adapter/StructuresAdapter.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/adapter/WorldPropertiesAdapter.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/adapter/WorldsAdapter.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/animation/OverscrollRefreshLayout.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/dialogs/ProfileSheet.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/fragments/ControllerSettingsFragment.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/fragments/SkinsSettingsFragment.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/splash/SplashBackgroundRegistry.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/splash/SplashBackgrounds.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/splash/SplashInit.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/splash/SplashSpinner.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/splash/SplashTimeline.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/util/SceneTransitions.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/views/Controller3DView.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/views/ControllerIllustrationView.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/views/CurvePreviewView.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/views/LauncherCursorInput.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/views/LauncherCursorOverlay.java delete mode 100644 app/src/main/java/org/chimeramc/client/launcher/ui/views/StickPreviewView.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/animation/LaunchIntro.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/components/PodEntrance.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/components/UiAccessibility.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/components/UiButton.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/components/UiCard.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/components/UiChip.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/components/UiGlass.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/components/UiRailButton.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/components/UiRow.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/components/UiSearchField.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/components/UiSegmented.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/components/UiSlider.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/components/UiToggle.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/components/UiWindow.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/compose/account/AccountSection.kt delete mode 100644 app/src/main/java/org/chimeramc/client/ui/compose/account/AccountSectionHost.kt delete mode 100644 app/src/main/java/org/chimeramc/client/ui/compose/account/AccountSectionState.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/compose/caramel/CaramelControls.kt delete mode 100644 app/src/main/java/org/chimeramc/client/ui/compose/caramel/CaramelGlass.kt delete mode 100644 app/src/main/java/org/chimeramc/client/ui/compose/caramel/CaramelLabels.kt delete mode 100644 app/src/main/java/org/chimeramc/client/ui/compose/caramel/CaramelTheme.kt delete mode 100644 app/src/main/java/org/chimeramc/client/ui/compose/caramel/CaramelUtil.kt delete mode 100644 app/src/main/java/org/chimeramc/client/ui/compose/caramel/launch/CaramelLaunchHost.kt delete mode 100644 app/src/main/java/org/chimeramc/client/ui/compose/caramel/launch/CaramelLaunchScreen.kt delete mode 100644 app/src/main/java/org/chimeramc/client/ui/compose/caramel/launch/CaramelLaunchState.kt delete mode 100644 app/src/main/java/org/chimeramc/client/ui/compose/connect/ConnectionStatus.kt delete mode 100644 app/src/main/java/org/chimeramc/client/ui/compose/connect/ConnectionStatusHost.kt delete mode 100644 app/src/main/java/org/chimeramc/client/ui/compose/launch/LaunchHeroState.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/compose/launch/PreviewBackground.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/fragments/InstallationsFragment.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/fragments/InstancesFragment.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/fragments/ReplayFragment.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/navigation/LauncherTab.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/navigation/NavAccent.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/navigation/NavBoundsOverlay.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/navigation/NavDebugBounds.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/navigation/NavLayoutMode.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/navigation/NavRailPolicy.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/navigation/NavTabBadge.java delete mode 100644 app/src/main/java/org/chimeramc/client/ui/navigation/NavTabLongPress.java delete mode 100644 app/src/main/res/drawable-nodpi/launch_bg_1.jpg delete mode 100644 app/src/main/res/drawable-nodpi/launch_bg_2.jpg delete mode 100644 app/src/main/res/drawable-nodpi/launch_bg_3.jpg delete mode 100644 app/src/main/res/drawable-nodpi/splash_bg_1.jpg delete mode 100644 app/src/main/res/drawable-nodpi/splash_bg_2.jpg delete mode 100644 app/src/main/res/drawable-nodpi/splash_bg_3.jpg delete mode 100644 app/src/main/res/drawable-nodpi/splash_bg_4.jpg delete mode 100644 app/src/main/res/drawable-nodpi/splash_bg_5.jpg delete mode 100644 app/src/main/res/drawable-nodpi/splash_bg_6.png delete mode 100644 app/src/main/res/drawable-nodpi/splash_bg_7.png delete mode 100644 app/src/main/res/drawable-nodpi/splash_bg_8.png delete mode 100644 app/src/main/res/drawable/bg_dock_play.xml delete mode 100644 app/src/main/res/drawable/bg_dock_version_chip.xml delete mode 100644 app/src/main/res/drawable/bg_launch_button.xml delete mode 100644 app/src/main/res/drawable/bg_launch_dock.xml delete mode 100644 app/src/main/res/drawable/bg_launch_quickdock.xml delete mode 100644 app/src/main/res/drawable/bg_logo_glow.xml delete mode 100644 app/src/main/res/drawable/bg_mod_menu_fab.xml delete mode 100644 app/src/main/res/drawable/bg_mod_menu_toggle.xml delete mode 100644 app/src/main/res/drawable/bg_split_launch.xml delete mode 100644 app/src/main/res/drawable/bg_vip_icon_button.xml delete mode 100644 app/src/main/res/drawable/bg_vip_surface.xml delete mode 100644 app/src/main/res/drawable/ic_nav_instances.xml delete mode 100644 app/src/main/res/drawable/ic_nav_launch.xml delete mode 100644 app/src/main/res/drawable/ic_nav_settings.xml delete mode 100644 app/src/main/res/drawable/nav_active_indicator.xml delete mode 100644 app/src/main/res/drawable/nav_rail_background.xml delete mode 100644 app/src/main/res/drawable/nav_tab_dot.xml delete mode 100644 app/src/main/res/drawable/splash_scrim.xml delete mode 100644 app/src/main/res/layout/activity_about.xml delete mode 100644 app/src/main/res/layout/activity_accounts.xml delete mode 100644 app/src/main/res/layout/activity_changelog.xml delete mode 100644 app/src/main/res/layout/activity_content_details.xml delete mode 100644 app/src/main/res/layout/activity_content_list.xml delete mode 100644 app/src/main/res/layout/activity_content_management.xml delete mode 100644 app/src/main/res/layout/activity_controller.xml delete mode 100644 app/src/main/res/layout/activity_crash.xml delete mode 100644 app/src/main/res/layout/activity_curseforge.xml delete mode 100644 app/src/main/res/layout/activity_custom_flat_world.xml delete mode 100644 app/src/main/res/layout/activity_customize.xml delete mode 100644 app/src/main/res/layout/activity_diagnostics.xml delete mode 100644 app/src/main/res/layout/activity_installations.xml delete mode 100644 app/src/main/res/layout/activity_instance_settings.xml delete mode 100644 app/src/main/res/layout/activity_instances.xml delete mode 100644 app/src/main/res/layout/activity_library.xml delete mode 100644 app/src/main/res/layout/activity_main.xml delete mode 100644 app/src/main/res/layout/activity_mod_config.xml delete mode 100644 app/src/main/res/layout/activity_mod_detail.xml delete mode 100644 app/src/main/res/layout/activity_mod_hub.xml delete mode 100644 app/src/main/res/layout/activity_modrinth_detail.xml delete mode 100644 app/src/main/res/layout/activity_mods_fullscreen.xml delete mode 100644 app/src/main/res/layout/activity_msft_login.xml delete mode 100644 app/src/main/res/layout/activity_options_editor.xml delete mode 100644 app/src/main/res/layout/activity_quick_launch.xml delete mode 100644 app/src/main/res/layout/activity_settings.xml delete mode 100644 app/src/main/res/layout/activity_setup_wizard.xml delete mode 100644 app/src/main/res/layout/activity_skins.xml delete mode 100644 app/src/main/res/layout/activity_splash.xml delete mode 100644 app/src/main/res/layout/activity_voice_chat.xml delete mode 100644 app/src/main/res/layout/activity_world_editor.xml delete mode 100644 app/src/main/res/layout/dialog_add_server.xml delete mode 100644 app/src/main/res/layout/dialog_connect_server.xml delete mode 100644 app/src/main/res/layout/dialog_custom_uri.xml delete mode 100644 app/src/main/res/layout/dialog_file_list.xml delete mode 100644 app/src/main/res/layout/dialog_load_world.xml delete mode 100644 app/src/main/res/layout/dialog_marketplace_pass.xml delete mode 100644 app/src/main/res/layout/dialog_realm_invite.xml delete mode 100644 app/src/main/res/layout/dialog_skin_import.xml delete mode 100644 app/src/main/res/layout/dialog_slash_command.xml delete mode 100644 app/src/main/res/layout/dialog_structure_list.xml delete mode 100644 app/src/main/res/layout/dialog_transfer_content.xml delete mode 100644 app/src/main/res/layout/fragment_replay.xml delete mode 100644 app/src/main/res/layout/item_curseforge_content.xml delete mode 100644 app/src/main/res/layout/item_curseforge_footer.xml delete mode 100644 app/src/main/res/layout/item_download_file.xml delete mode 100644 app/src/main/res/layout/item_file_detail.xml delete mode 100644 app/src/main/res/layout/item_flat_layer.xml delete mode 100644 app/src/main/res/layout/item_installation_version.xml delete mode 100644 app/src/main/res/layout/item_instance_card.xml delete mode 100644 app/src/main/res/layout/item_mod.xml delete mode 100644 app/src/main/res/layout/item_mod_config_node.xml delete mode 100644 app/src/main/res/layout/item_mod_detail_meta_row.xml delete mode 100644 app/src/main/res/layout/item_modrinth_version.xml delete mode 100644 app/src/main/res/layout/item_msft_account.xml delete mode 100644 app/src/main/res/layout/item_option_property.xml delete mode 100644 app/src/main/res/layout/item_property_header.xml delete mode 100644 app/src/main/res/layout/item_quick_launch.xml delete mode 100644 app/src/main/res/layout/item_resource_pack.xml delete mode 100644 app/src/main/res/layout/item_screenshot.xml delete mode 100644 app/src/main/res/layout/item_server.xml delete mode 100644 app/src/main/res/layout/item_skin_pack.xml delete mode 100644 app/src/main/res/layout/item_structure.xml delete mode 100644 app/src/main/res/layout/item_voice_channel.xml delete mode 100644 app/src/main/res/layout/item_world.xml delete mode 100644 app/src/main/res/layout/item_world_property.xml delete mode 100644 app/src/main/res/layout/nav_bar.xml delete mode 100644 app/src/main/res/layout/nav_rail.xml delete mode 100644 app/src/main/res/layout/overlay_mod_menu_button.xml delete mode 100644 app/src/main/res/layout/overlay_pause_menu_row.xml delete mode 100644 app/src/main/res/layout/popup_account_switch.xml delete mode 100644 app/src/main/res/layout/spinner_dropdown_item.xml delete mode 100644 app/src/main/res/layout/spinner_font_item.xml delete mode 100644 app/src/main/res/layout/spinner_item.xml delete mode 100644 app/src/test/java/org/chimeramc/client/core/auth/MsftLoginFlowTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/core/mods/inbuilt/overlay/CursorControllerTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/core/mods/inbuilt/overlay/CursorRouterTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/core/mods/inbuilt/overlay/ModMenuMetricsTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/core/mods/inbuilt/overlay/ModMenuPaletteTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/core/mods/inbuilt/overlay/ModMenuSettingsTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/core/mods/inbuilt/overlay/PauseMenuActionsTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/core/mods/inbuilt/overlay/QuickSettingsTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/core/setup/SetupWizardShowTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/core/voice/VoiceRelaySettingsRemovedTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/launcher/core/mods/inbuilt/overlay/ModMenuNavLayoutTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/launcher/core/mods/inbuilt/overlay/ModUnavailableReasonResourcesTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/launcher/ui/activities/LaunchDockStyleTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/launcher/ui/activities/LaunchTabResourcesTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/launcher/ui/activities/SettingsBindingTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/launcher/ui/splash/SplashBackgroundsTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/launcher/ui/views/LaunchRingPaletteTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/activities/SetupWizardLastTabsTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/activities/SetupWizardPopupTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/activities/SetupWizardResourcesTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/activities/SetupWizardStateTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/animation/LaunchIntroTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/components/PodEntranceTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/components/SkeletonPlaceholderTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/components/UiAccessibilityTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/components/UiButtonPressTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/components/UiGlassTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/compose/account/AccountSectionStateTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/compose/caramel/CaramelPaletteParityTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/compose/connect/ConnectionStatusTest.kt delete mode 100644 app/src/test/java/org/chimeramc/client/ui/compose/launch/LaunchHeroStateTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/compose/launch/LaunchTabLayoutTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/compose/launch/PreviewBackgroundTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/compose/theme/ComposeGlassSurfaceTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/navigation/LauncherTabTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/navigation/NavAccentTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/navigation/NavDebugBoundsGateTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/navigation/NavDebugBoundsTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/navigation/NavFocusOrderTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/navigation/NavLayoutModeTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/navigation/NavRailPolicyTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/navigation/NavShellResourcesTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/navigation/NavTabBadgeTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/navigation/NavTabLongPressTest.java delete mode 100644 app/src/test/java/org/chimeramc/client/ui/views/LauncherCursorInputTest.java diff --git a/.openhands/memory/2026-11-14.md b/.openhands/memory/2026-11-14.md new file mode 100644 index 00000000..b872e509 --- /dev/null +++ b/.openhands/memory/2026-11-14.md @@ -0,0 +1,83 @@ +# 2026-11-14 + +## Task: remove the legacy UI (screens/mod menu/splash/tabs/rail/buttons) for a new UI drop-in + +### Toolchain (this container had none) +Reinstalled per `docs/agents/recovering-the-build-toolchain-in-a-fresh-container.md`: +`/workspace/tools/jdk-21.0.13+9` (Temurin 21.0.13+9) + `/workspace/android-sdk` +(platform-tools, android-35/36, build-tools 35/36, ndk 28.2.13676358, cmake 3.22.1). +`local.properties` -> `sdk.dir=/workspace/android-sdk`. +**`assembleDebug` additionally needs the NESTED submodules** — plain `git submodule update --init` +leaves `libHttpClient/External/zlib` empty and CMake fails at +`libHttpClient.Android/CMakeLists.txt:127` ("Cannot find source file: .../External/zlib/adler32.c", +"No SOURCES given to target: libHttpClient.Android"). Fix: `git submodule update --init --recursive`. + +### Baseline at HEAD 6b83ec5 (green) +`:app:testDebugUnitTest` = 3456 tests, 0 failures, 1 skipped. `:app:assembleDebug` SUCCESSFUL (~4 min). + +### Coupling finding (the crux of this task) +- The manifest lists `org.chimeramc.client.ui.activities.*`, but those files physically live in + `app/src/main/java/org/chimeramc/client/launcher/ui/activities/` — i.e. **the directory tree does + not match the package tree** for the whole `launcher/ui` tree (package decls are + `org.chimeramc.client.ui.*`). Operate on FILES, never assume dir==package. +- `client.ui` + `client.launcher.ui` = 144 files across 21 packages (~40k LOC), 94 layouts. +- **No engine/core package imports the UI**, but **41 kept files reference deleted UI symbols** + (DynamicAnim, CustomAlertDialog, UiTokens, MainViewModel, the Activity classes as Intent targets, + etc.). Those are the rewiring surface. + +## In-game Mod Menu deletion (task 2) +The in-game menu had **three layers**; all three are now cut: +1. **Native ImGui** in the sweetloader submodule: `src/pl/menu/` (MenuTheme/Widgets/Render/Filter/ + Host/Jni, ~1900 LOC). It was self-contained — the ONLY external consumer was + `src/pl/render/PresentFrameEffect.cpp::OnPresentBoundary` (`pl::menu::IsOpen/IsMenuLive/Frame`). + Removed: the six `src/pl/menu/*.cpp` from `CMakeLists.txt`, the `add_library(imgui …)` target + + its `target_link_libraries(sweetloader … imgui)`, the present-hook call, `third_party/imgui/` + (2.8 MB, vendored, only consumer was the menu) and its NOTICE attribution, and + `tests/cosmetics/MenuFilterTest.cpp` + its `run_tests.sh` block. + Submodule gates that must stay green: `scripts/check_sources_listed.py` (CMake sources ↔ + `src/**/*.cpp` ↔ `run_tests.sh`), `scripts/check_pinned_deps.py` (NOTICE ↔ fetched pins; the + vendored imgui block is NOT checked by it, but remove it anyway for honesty), host suite + `bash tests/cosmetics/run_tests.sh`. +2. **Java bridge**: `launcher.preloader.PreloaderMenu` + `core.mods.inbuilt.overlay.InGameMenuController` + (both already deleted). +3. **Launcher overlay widgets** (content panels behind the native menu): deleted + `MoreButtonsEditor`, `VoicePanel`, `VoiceUiKit`, `CosmeticsPanel` + their tests + (`PauseMenuActionsTest`, `QuickSettingsTest`, `ModMenuMetricsTest`, `ModMenuPaletteTest`, + `ModMenuSettingsTest`, `CursorControllerTest`, `CursorRouterTest`, `ModMenuNavLayoutTest`, + `ModUnavailableReasonResourcesTest`). +Rewiring done to keep the systems compiling: +- `MinecraftActivity.kt`: dropped `InGameMenuController.install/release/getInstance().handleTouch/ + drainAndApply` and the `MoreButtonsEditor.onActivityResult/closeEditor/onResume` hooks. +- `InbuiltModuleProvider.java`: `ModIds.MORE_BUTTONS` no longer opens an editor (null action). +- `InbuiltModManager.java`: removed `get/setModMenuSettings` (they referenced the deleted + `ModMenuSettings`). Kept the appearance prefs/keys and `getModMenuButtonOpacity`. +- `InbuiltOverlayManager.java`: removed `modMenuButton`/`pauseMenuRow`/`DebugMenuOverlay`… (DebugMenu + is the F3 overlay MOD, NOT the menu — it stays). + +## Legacy visual-asset cleanup (task 1 tail) +Deleted 29 dead legacy drawables (splash art, launch/dock buttons, nav rail): `splash_bg_*` (8), +`launch_bg_*` (3), `splash_scrim`, `bg_logo_glow`, `bg_launch_button`, `bg_split_launch`, +`bg_mod_menu_fab/toggle`, `bg_vip_icon_button`, `ic_nav_*` (3), `nav_active_indicator`, +`nav_rail_background`, `nav_tab_dot`, `bg_dock_play`, `bg_dock_version_chip`, `bg_launch_dock`, +`bg_launch_quickdock`, `bg_vip_surface`. Also removed the dead `Widget.Caramel.NavTab/NavRailTab/ +QuickDock*` styles (86 lines). Kept 60+ generic drawables + the whole token/style system a new UI +would reuse (bg_*, ic_*, glass_card, Widget.Caramel.Card*, TextAppearance.Caramel.*). +Two tests pinned the removed button drawables — trimmed both (not deleted): `ButtonPressDrawableTest` +(now only `button_accent.xml`) and `RadiusScaleTest`'s surface list (dropped the 3 gone surfaces). + +## TRAP: regex `` removal over-matched +Removing `Widget.Caramel.QuickDock` (a **self-closing** `')` pattern ran PAST it to the next real `` and ate +the `TextAppearance.Caramel` base style + its doc comment. Recovered by restoring the block from +`git show HEAD:…`. Lesson: match self-closing styles explicitly (`/>` vs ``). + +## Final verification (both repos) +- Parent: `:app:testDebugUnitTest` = **3164 tests, 0 failures**, `:app:lintDebug` clean, + `:app:assembleDebug` SUCCESSFUL (clean `app/.cxx` rebuild included, produces + `libsweetloader.so`). `scripts/check_ui_hygiene.py` clean (findViewById baseline 32→29 after + `--update`). Layouts 94→20; drawables ~167→161 distinct names. +- Submodule: host suite passes; `check_sources_listed.py` + `check_pinned_deps.py` green. +- Parent diff: **286 files, +13 / −57496**. Submodule diff: **30 files, −54879**. +- Note: `git status` counts differ from `diff --stat` because many deletions were staged with + `git rm` (staged) while others were plain `rm` (unstaged) — use `git diff --stat HEAD` for the + true total. diff --git a/.openhands/memory/MEMORY.md b/.openhands/memory/MEMORY.md index d9139e7a..1f387287 100644 --- a/.openhands/memory/MEMORY.md +++ b/.openhands/memory/MEMORY.md @@ -10,6 +10,28 @@ Index only. Detail and session notes live in the daily logs (`YYYY-MM-DD.md`). The repo was called **GlowberryClient** before; the package is `org.chimeramc.client` either way. +### UI skeleton state (2026-11-14: the whole legacy UI was deleted for a new one) +The launcher UI + the in-game Mod Menu UI were removed in one sweep, keeping every system. Do not +re-add the old UI; a new one is being dropped in. See the 2026-11-14 daily log for the full list. +- **`ui.activities` files live under `launcher/ui/activities/`** (dir ≠ package for the whole + `launcher/ui` tree — package decls are `org.chimeramc.client.ui.*`). Operate on FILES, not dirs. +- 7 placeholder Activities remain (`BaseActivity` + `MainActivity`/`SplashActivity`/`LibraryActivity`/ + `SettingsActivity`/`ModsFullscreenActivity`/`CrashActivity`); they draw nothing but are manifest + targets. `MainActivity.setTextMinecraftVersion()` and `LibraryActivity.EXTRA_INITIAL_TAB`/`TAB_*` + are kept no-op API other code still calls. Layouts 94→20; the kept 20 are the game-host/HUD ones. +- **The in-game menu was 3 layers, all cut**: native ImGui (`sweetloader/src/pl/menu/` + vendored + `third_party/imgui/`, consumed only by `PresentFrameEffect.cpp`), the Java bridge + (`PreloaderMenu`/`InGameMenuController`), and the launcher overlay widgets (`MoreButtonsEditor`, + `VoicePanel`/`VoiceUiKit`, `CosmeticsPanel`). `DebugMenuOverlay` is the F3 overlay MOD — keep it. +- **Kept (a new UI will reuse them)**: `UiTokens`, `Widget.Caramel.Card*`, `TextAppearance.Caramel.*`, + `GlowDimens`, generic `bg_*`/`ic_*` drawables, `PersonalizationManager`, all `core.*` systems. + Removed only the dead legacy chrome (`splash_bg_*`, `launch_bg_*`, dock/nav drawables + styles). +- Gate that catches dead drawables: `LayoutResourceTest` (unreferenced layout) fails the build — + delete the layout. Hygiene: `scripts/check_ui_hygiene.py --update` retires stale findViewById + baseline rows and shrinks the baseline when a file is deleted. +- **Trap**: a regex `" + - "" + - "" + - result + - ""; - summary.loadDataWithBaseURL("https://www.curseforge.com", htmlData, "text/html", "utf-8", null); - } - - }); - } - - @Override - public void onError(Throwable t) { - } - }); - } - - private void onInstallClick() { - loadAllFiles(); - } - - private void loadAllFiles() { - progressBar.setVisibility(View.VISIBLE); - btnInstall.setEnabled(false); - - client.getModFiles(content.id, 0, 50, new CurseForgeClient.CurseForgeCallback<>() { - @Override - public void onSuccess(org.chimeramc.client.core.curseforge.models.ModFilesResponse result) { - runOnUiThread(() -> { - progressBar.setVisibility(View.GONE); - btnInstall.setEnabled(true); - if (result != null && result.data != null) { - showFilesDialog(result.data); - } else { - Toast.makeText(ContentDetailsActivity.this, R.string.no_files_found, Toast.LENGTH_SHORT).show(); - } - }); - } - - @Override - public void onError(Throwable t) { - runOnUiThread(() -> { - progressBar.setVisibility(View.GONE); - btnInstall.setEnabled(true); - if (t instanceof CurseForgeClient.MissingApiKeyException) { - org.chimeramc.client.core.curseforge.CurseForgeKeyDialog.show( - ContentDetailsActivity.this, ContentDetailsActivity.this::loadAllFiles); - return; - } - Toast.makeText(ContentDetailsActivity.this, - UserFacingError.reason(ContentDetailsActivity.this, t), - Toast.LENGTH_LONG).show(); - }); - } - }); - } - - private void showFilesDialog(java.util.List files) { - com.google.android.material.bottomsheet.BottomSheetDialog dialog = new com.google.android.material.bottomsheet.BottomSheetDialog(this); - dialog.setContentView(R.layout.dialog_file_list); - - org.chimeramc.client.util.PersonalizationManager pm = new org.chimeramc.client.util.PersonalizationManager(this); - int accent = pm.getAccentColor(); - - RecyclerView recycler = dialog.findViewById(R.id.recycler_files); - - org.chimeramc.client.ui.adapter.FileListAdapter adapter = new org.chimeramc.client.ui.adapter.FileListAdapter(file -> { - dialog.dismiss(); - downloadAndImport(file); - }, accent); - - recycler.setLayoutManager(new LinearLayoutManager(this)); - recycler.setAdapter(adapter); - adapter.setFiles(files); - - if (dialog.getWindow() != null) { - dialog.getWindow().setFlags(android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); - } - - dialog.show(); - - if (dialog.getWindow() != null) { - android.view.View decorView = dialog.getWindow().getDecorView(); - decorView.setSystemUiVisibility( - android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - | android.view.View.SYSTEM_UI_FLAG_FULLSCREEN - | android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE - ); - if (ApiLevels.atLeastR()) { - dialog.getWindow().setDecorFitsSystemWindows(false); - android.view.WindowInsetsController controller = decorView.getWindowInsetsController(); - if (controller != null) { - controller.setSystemBarsBehavior(android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE); - controller.hide(android.view.WindowInsets.Type.statusBars() | android.view.WindowInsets.Type.navigationBars()); - } - } - dialog.getWindow().clearFlags(android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); - } - - dialog.getBehavior().setState(com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED); - } - - private void onBrowserClick() { - if (content.links != null && content.links.websiteUrl != null) { - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(content.links.websiteUrl)); - startActivity(intent); - } else { - Toast.makeText(this, R.string.no_website_link_available, Toast.LENGTH_SHORT).show(); - } - } - - private void downloadAndImport(ContentFile file) { - runOnUiThread(() -> { - progressBar.setVisibility(View.VISIBLE); - progressBar.setIndeterminate(true); - btnInstall.setEnabled(false); - Toast.makeText(this, getString(R.string.curseforge_downloading), Toast.LENGTH_SHORT).show(); - }); - - if (!runOnDownloadExecutor(() -> { - try { - File cacheDir = getCacheDir(); - File outputFile = new File(cacheDir, file.fileName); - - OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); - org.chimeramc.client.settings.LowLatencyNetworkManager.configure(clientBuilder); - OkHttpClient client = clientBuilder.build(); - Request request = new Request.Builder().url(file.downloadUrl).build(); - - try (Response response = client.newCall(request).execute()) { - if (!response.isSuccessful()) throw new IOException("Failed to download"); - - try (InputStream is = response.body().byteStream(); - FileOutputStream fos = new FileOutputStream(outputFile)) { - byte[] buffer = new byte[8192]; - int read; - while ((read = is.read(buffer)) != -1) { - fos.write(buffer, 0, read); - } - } - } - - runOnUiThread(() -> { - Toast.makeText(this, getString(R.string.curseforge_importing), Toast.LENGTH_SHORT).show(); - importFile(outputFile); - }); - - } catch (Throwable e) { - // Throwable, not Exception: this handler is the only thing that clears the progress - // bar and re-enables Install, and a CurseForge download buffers a whole pack in - // memory - an OutOfMemoryError skipped it and left the screen stuck mid-download. - runOnUiThread(() -> { - progressBar.setVisibility(View.GONE); - btnInstall.setEnabled(true); - Toast.makeText(this, getString(R.string.curseforge_download_failed) + "\n\n" - + UserFacingError.reason(this, e), Toast.LENGTH_LONG).show(); - }); - } - })) { - // onDestroy shut the download pool down while the Install tap was being handled. - progressBar.setVisibility(View.GONE); - btnInstall.setEnabled(true); - } - } - - /** - * Submits a CurseForge download, tolerating a pool {@link #onDestroy()} has shut down. - * - *

Install is a button, so a tap that lands as the screen closes would otherwise throw - * {@link java.util.concurrent.RejectedExecutionException} on the main thread. Returns false when - * the work could not be submitted. - */ - private boolean runOnDownloadExecutor(Runnable work) { - return SafeExecutor.submit(downloadExecutor, work); - } - - private void importFile(File file) { - Uri uri = Uri.fromFile(file); - - GameVersion currentVersion = versionManager.getSelectedVersion(); - if (currentVersion == null) { - progressBar.setVisibility(View.GONE); - btnInstall.setEnabled(true); - Toast.makeText(this, R.string.not_found_version, Toast.LENGTH_SHORT).show(); - return; - } - - File worldsDir = getWorldsDirectory(); - File resourcePacksDir = getPackDirectory("resource_packs"); - File behaviorPacksDir = getPackDirectory("behavior_packs"); - File skinPacksDir = getPackDirectory("skin_packs"); - - contentImporter.importContent(java.util.Collections.singletonList(uri), resourcePacksDir, behaviorPacksDir, skinPacksDir, worldsDir, - new ContentImporter.ImportCallback() { - @Override - public void onSuccess(String message) { - runOnUiThread(() -> { - progressBar.setVisibility(View.GONE); - btnInstall.setEnabled(true); - Toast.makeText(ContentDetailsActivity.this, message, Toast.LENGTH_SHORT).show(); - if (file.exists()) file.delete(); - }); - } - - @Override - public void onError(String error) { - runOnUiThread(() -> { - progressBar.setVisibility(View.GONE); - btnInstall.setEnabled(true); - Toast.makeText(ContentDetailsActivity.this, error, Toast.LENGTH_LONG).show(); - }); - } - - @Override - public void onProgress(int progress) { - } - }); - } - private File getPackDirectory(String packType) { - File gameDataDir = getGameDataDirForSavedStorageType(); - return gameDataDir == null ? null : new File(gameDataDir, packType); - } - - private File getWorldsDirectory() { - File gameDataDir = getGameDataDirForSavedStorageType(); - return gameDataDir == null ? null : new File(gameDataDir, "minecraftWorlds"); - } - - private File getGameDataDirForSavedStorageType() { - android.content.SharedPreferences prefs = getSharedPreferences("content_management", MODE_PRIVATE); - String savedType = prefs.getString("storage_type", "INTERNAL"); - FeatureSettings.StorageType currentStorageType = parseStorageType(savedType); - GameVersion currentVersion = versionManager.getSelectedVersion(); - if (currentVersion == null) return null; - currentStorageType = LauncherStorage.normalizeContentStorageType( - currentStorageType, - currentVersion.versionIsolation - ); - return LauncherStorage.getContentGameDataDir(this, currentVersion.getStorageProfileId(), currentStorageType); - } - - private FeatureSettings.StorageType parseStorageType(String value) { - try { - return FeatureSettings.StorageType.valueOf(value); - } catch (Exception ignored) { - return FeatureSettings.StorageType.INTERNAL; - } - } - - private String getHexColor(int colorResId) { - int color = getResources().getColor(colorResId, getTheme()); - return String.format("#%06X", (0xFFFFFF & color)); - } -} diff --git a/app/src/main/java/org/chimeramc/client/launcher/ui/activities/ContentListActivity.java b/app/src/main/java/org/chimeramc/client/launcher/ui/activities/ContentListActivity.java deleted file mode 100644 index 96ccb632..00000000 --- a/app/src/main/java/org/chimeramc/client/launcher/ui/activities/ContentListActivity.java +++ /dev/null @@ -1,1326 +0,0 @@ -package org.chimeramc.client.ui.activities; - -import android.content.Intent; -import android.content.SharedPreferences; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.util.Log; -import android.view.View; -import android.widget.EditText; -import android.widget.RadioButton; -import android.widget.RadioGroup; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.appcompat.app.AlertDialog; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.LinearLayoutManager; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; - -import org.chimeramc.client.R; -import org.chimeramc.client.core.content.ContentManager; -import org.chimeramc.client.core.content.ResourcePackItem; -import org.chimeramc.client.core.content.ResourcePackManager; -import org.chimeramc.client.core.content.RakNetPing; -import org.chimeramc.client.core.content.ServerItem; -import org.chimeramc.client.core.content.ServerPongInfo; -import org.chimeramc.client.core.content.StructureExtractor; -import org.chimeramc.client.core.content.WorldItem; -import org.chimeramc.client.core.content.WorldManager; -import org.chimeramc.client.core.versions.GameVersion; -import org.chimeramc.client.core.versions.VersionManager; -import org.chimeramc.client.databinding.ActivityContentListBinding; -import org.chimeramc.client.settings.FeatureSettings; -import org.chimeramc.client.ui.adapter.ResourcePacksAdapter; -import org.chimeramc.client.ui.adapter.StructuresAdapter; -import org.chimeramc.client.ui.adapter.WorldsAdapter; -import org.chimeramc.client.ui.animation.DynamicAnim; -import org.chimeramc.client.ui.dialogs.CustomAlertDialog; -import org.chimeramc.client.util.LauncherStorage; - -import android.provider.MediaStore; -import android.content.ContentValues; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import java.io.OutputStream; -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.stream.Collectors; -import org.chimeramc.client.util.AppLog; -import org.chimeramc.client.util.AppExecutors; -import org.chimeramc.client.util.MainThread; - -public class ContentListActivity extends BaseActivity { - - private static final String TAG = "ContentListActivity"; - - public static final String EXTRA_CONTENT_TYPE = "content_type"; - public static final String EXTRA_WORLDS_DIRECTORY = "worlds_directory"; - public static final String EXTRA_CURRENT_STORAGE_TYPE = "current_storage_type"; - public static final int TYPE_WORLDS = 0; - public static final int TYPE_SKIN_PACKS = 1; - public static final int TYPE_RESOURCE_PACKS = 2; - public static final int TYPE_BEHAVIOR_PACKS = 3; - public static final int TYPE_SCREENSHOTS = 4; - public static final int TYPE_SERVERS = 5; - - private ActivityContentListBinding binding; - private ContentManager contentManager; - private VersionManager versionManager; - private int contentType; - private File worldsDirectory; - private FeatureSettings.StorageType currentStorageType; - - private WorldsAdapter worldsAdapter; - private ResourcePacksAdapter packsAdapter; - private org.chimeramc.client.ui.adapter.ScreenshotsAdapter screenshotsAdapter; - private org.chimeramc.client.ui.adapter.ServersAdapter serversAdapter; - - private ActivityResultLauncher exportLauncher; - private ActivityResultLauncher exportPackLauncher; - private ActivityResultLauncher customFlatWorldLauncher; - private ActivityResultLauncher structureExportLauncher; - private WorldItem pendingExportWorld; - private ResourcePackItem pendingExportPack; - private WorldItem pendingStructureExportWorld; - private StructureExtractor.StructureInfo pendingStructureInfo; - private StructureExtractor structureExtractor; - - private List allWorlds = new ArrayList<>(); - private List allPacks = new ArrayList<>(); - private List allServers = new ArrayList<>(); - - private org.chimeramc.client.ui.dialogs.LoadingDialog progressDialog; - - private final Handler mainHandler = MainThread.handler(); - private ExecutorService pingExecutor; - /** Server addresses ("ip:port") that already answered; they are not re-pinged every refresh. */ - private final java.util.Set pingedServerKeys = new java.util.HashSet<>(); - /** Starred server keys, read once and kept in step with the store. */ - private java.util.Set favouriteServerKeys = java.util.Collections.emptySet(); - - private void showProgressDialog(String message) { - // Showing a dialog on a finishing Activity throws WindowManager.BadTokenException, which - // took the screen down instead of reporting whatever it was about to report. The operation - // still runs; only the dialog is skipped. - if (isFinishing() || isDestroyed()) { - return; - } - if (progressDialog == null) { - progressDialog = new org.chimeramc.client.ui.dialogs.LoadingDialog(this); - } - progressDialog.show(); - progressDialog.setMessage(message); - } - - private void hideProgressDialog() { - if (progressDialog != null && progressDialog.isShowing()) { - progressDialog.dismiss(); - } - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - binding = ActivityContentListBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - DynamicAnim.applyPressScaleRecursively(binding.getRoot()); - - contentType = getIntent().getIntExtra(EXTRA_CONTENT_TYPE, TYPE_WORLDS); - - // Guest mode removes the server list. This screen can be reached by a deep link - // or a restore as well as from Content Management, so the refusal lives here rather than - // only on the button that opens it; showing the category and then failing to load it would - // be worse than not opening it at all. - if (contentType == TYPE_SERVERS - && org.chimeramc.client.core.privacy.PrivacyModes.current(this).hidesServers()) { - finish(); - return; - } - contentManager = ContentManager.getInstance(this); - versionManager = VersionManager.get(this); - - String storageTypeStr = getIntent().getStringExtra(EXTRA_CURRENT_STORAGE_TYPE); - if (storageTypeStr != null) { - currentStorageType = parseStorageType(storageTypeStr); - } else { - SharedPreferences prefs = getSharedPreferences("content_management", MODE_PRIVATE); - String savedType = prefs.getString("storage_type", "INTERNAL"); - currentStorageType = parseStorageType(savedType); - } - - setupActivityResultLaunchers(); - setupUI(); - setupObservers(); - // ContentListActivity can be the first screen in the process (deep link, restore after a - // process death, or opened straight from another app), and when it is, nobody has pointed - // ContentManager at the game's directories yet. ServerManager then has a null - // minecraftPeDir, so every add/delete server reported failure while worlds and packs - // happened to work. Pointing it at this screen's own resolution is self-sufficient and - // idempotent, so it also repairs the case where MainActivity wired a different storage - // type than the one the user picked here. - updateStorageDirectories(); - loadContent(); - } - - /** - * Wires ContentManager to the directories for this screen's current storage type. - * - * The directories are derived from the same {@code getGameDataDirForType} resolution the - * worlds and packs already use, so servers cannot drift onto a different root. It is a no-op - * when no instance is selected. - */ - private void updateStorageDirectories() { - File gameDataDir = getGameDataDirForType(currentStorageType); - if (gameDataDir == null) { - return; - } - contentManager.setStorageDirectories( - new File(gameDataDir, "minecraftWorlds"), - new File(gameDataDir, "resource_packs"), - new File(gameDataDir, "behavior_packs"), - new File(gameDataDir, "skin_packs"), - new File(gameDataDir, "Screenshots"), - new File(gameDataDir, "minecraftpe")); - } - - private void setupActivityResultLaunchers() { - exportLauncher = registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), - result -> { - if (result.getResultCode() == RESULT_OK && result.getData() != null && pendingExportWorld != null) { - Uri uri = result.getData().getData(); - if (uri != null) { - exportWorld(pendingExportWorld, uri); - } - } - pendingExportWorld = null; - } - ); - - exportPackLauncher = registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), - result -> { - if (result.getResultCode() == RESULT_OK && result.getData() != null && pendingExportPack != null) { - Uri uri = result.getData().getData(); - if (uri != null) { - exportPack(pendingExportPack, uri); - } - } - pendingExportPack = null; - } - ); - - customFlatWorldLauncher = registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), - result -> { - if (result.getResultCode() == RESULT_OK) { - loadContent(); - } - } - ); - - structureExportLauncher = registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), - result -> { - if (result.getResultCode() == RESULT_OK && result.getData() != null && pendingStructureExportWorld != null && pendingStructureInfo != null) { - Uri uri = result.getData().getData(); - if (uri != null) { - exportStructureToFile(pendingStructureExportWorld, pendingStructureInfo, uri); - } - } - pendingStructureExportWorld = null; - pendingStructureInfo = null; - } - ); - - structureExtractor = new StructureExtractor(this); - } - - private void setupUI() { - - String worldsPath = getIntent().getStringExtra(EXTRA_WORLDS_DIRECTORY); - if (worldsPath != null) { - worldsDirectory = new File(worldsPath); - } else { - worldsDirectory = getWorldsDirectoryForType(currentStorageType); - } - - switch (contentType) { - case TYPE_WORLDS: - binding.titleText.setText(getString(R.string.worlds_title)); - binding.customFlatButton.setVisibility(View.VISIBLE); - setupWorldsRecyclerView(); - break; - case TYPE_SKIN_PACKS: - binding.titleText.setText(getString(R.string.skin_packs_title)); - setupPacksRecyclerView(); - break; - case TYPE_RESOURCE_PACKS: - binding.titleText.setText(getString(R.string.resource_packs_title)); - setupPacksRecyclerView(); - break; - case TYPE_BEHAVIOR_PACKS: - binding.titleText.setText(getString(R.string.behavior_packs_title)); - setupPacksRecyclerView(); - break; - case TYPE_SCREENSHOTS: - binding.titleText.setText(getString(R.string.screenshots_category)); - binding.searchEditText.setVisibility(View.GONE); - setupScreenshotsRecyclerView(); - break; - case TYPE_SERVERS: - binding.titleText.setText(getString(R.string.servers_category)); - binding.searchEditText.setVisibility(View.VISIBLE); - binding.customFlatButton.setText(getString(R.string.quick_launch_add_server)); - binding.customFlatButton.setVisibility(View.VISIBLE); - // Read the stars before the adapter is built so the first paint is already - // favourites-first rather than reordering once the list arrives. - favouriteServerKeys = org.chimeramc.client.core.content.ServerFavouritesStore.load(this); - setupServersRecyclerView(); - break; - } - - binding.customFlatButton.setOnClickListener(v -> { - if (contentType == TYPE_SERVERS) { - showAddServerDialog(); - } else { - openCustomFlatWorld(); - } - }); - - setupSearchFilter(); - } - - private void setupSearchFilter() { - binding.searchEditText.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) {} - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - filterContent(s.toString()); - } - - @Override - public void afterTextChanged(Editable s) {} - }); - } - - private void filterContent(String query) { - String lowerQuery = query.toLowerCase().trim(); - boolean empty = false; - if (contentType == TYPE_WORLDS) { - if (lowerQuery.isEmpty()) { - worldsAdapter.updateWorlds(allWorlds); - } else { - List filtered = allWorlds.stream() - .filter(world -> world.getWorldName().toLowerCase().contains(lowerQuery)) - .collect(Collectors.toList()); - worldsAdapter.updateWorlds(filtered); - } - empty = worldsAdapter.getItemCount() == 0; - setEmptyState(empty, R.string.content_empty_worlds_title, R.string.content_empty_worlds_message); - } else if (contentType == TYPE_SERVERS) { - if (lowerQuery.isEmpty()) { - serversAdapter.updateData(allServers); - } else { - List filtered = allServers.stream() - .filter(server -> server.name.toLowerCase().contains(lowerQuery) || - server.ip.toLowerCase().contains(lowerQuery)) - .collect(Collectors.toList()); - serversAdapter.updateData(filtered); - } - empty = serversAdapter.getItemCount() == 0; - setEmptyState(empty, R.string.content_empty_servers_title, R.string.content_empty_servers_message); - } else if (contentType == TYPE_SCREENSHOTS) { - empty = screenshotsAdapter.getItemCount() == 0; - setEmptyState(empty, R.string.content_empty_screenshots_title, R.string.content_empty_screenshots_message); - } else { - if (lowerQuery.isEmpty()) { - packsAdapter.updateResourcePacks(allPacks); - } else { - List filtered = allPacks.stream() - .filter(pack -> pack.getPackName().toLowerCase().contains(lowerQuery)) - .collect(Collectors.toList()); - packsAdapter.updateResourcePacks(filtered); - } - empty = packsAdapter.getItemCount() == 0; - setEmptyState(empty, R.string.content_empty_packs_title, R.string.content_empty_packs_message); - } - } - - private void setEmptyState(boolean empty, int titleRes, int messageRes) { - View emptyContainer = binding.contentEmpty; - if (emptyContainer == null) return; - emptyContainer.setVisibility(empty ? View.VISIBLE : View.GONE); - if (empty) { - binding.contentRecyclerView.setVisibility(View.GONE); - binding.contentEmptyTitle.setText(getString(titleRes)); - binding.contentEmptyMessage.setText(getString(messageRes)); - } else { - binding.contentRecyclerView.setVisibility(View.VISIBLE); - } - } - - private void setupWorldsRecyclerView() { - worldsAdapter = new WorldsAdapter(); - worldsAdapter.setOnWorldActionListener(new WorldsAdapter.OnWorldActionListener() { - @Override - public void onWorldExport(WorldItem world) { - startWorldExport(world); - } - - @Override - public void onWorldDelete(WorldItem world) { - showDeleteWorldDialog(world); - } - - @Override - public void onWorldBackup(WorldItem world) { - backupWorld(world); - } - - @Override - public void onWorldEdit(WorldItem world) { - openWorldEditor(world); - } - - @Override - public void onWorldExtractStructures(WorldItem world) { - showExtractStructuresDialog(world); - } - - @Override - public void onWorldTransfer(WorldItem world) { - showTransferWorldDialog(world); - } - }); - - binding.contentRecyclerView.setLayoutManager(new LinearLayoutManager(this)); - binding.contentRecyclerView.setAdapter(worldsAdapter); - binding.contentRecyclerView.post(() -> DynamicAnim.staggerRecyclerChildren(binding.contentRecyclerView)); - } - - private void openWorldEditor(WorldItem world) { - File worldFile = world.getFile(); - if (worldFile == null || !worldFile.exists()) { - Toast.makeText(this, R.string.world_directory_not_found, Toast.LENGTH_SHORT).show(); - return; - } - - Intent intent = new Intent(this, WorldEditorActivity.class); - intent.putExtra(WorldEditorActivity.EXTRA_WORLD_PATH, worldFile.getAbsolutePath()); - intent.putExtra(WorldEditorActivity.EXTRA_WORLD_NAME, world.getWorldName()); - startActivity(intent); - } - - private void setupPacksRecyclerView() { - packsAdapter = new ResourcePacksAdapter(); - packsAdapter.setOnResourcePackActionListener(new ResourcePacksAdapter.OnResourcePackActionListener() { - @Override - public void onResourcePackDelete(ResourcePackItem pack) { - showDeletePackDialog(pack); - } - - @Override - public void onResourcePackTransfer(ResourcePackItem pack) { - showTransferPackDialog(pack); - } - - @Override - public void onResourcePackExport(ResourcePackItem pack) { - startPackExport(pack); - } - }); - - binding.contentRecyclerView.setLayoutManager(new LinearLayoutManager(this)); - binding.contentRecyclerView.setAdapter(packsAdapter); - binding.contentRecyclerView.post(() -> DynamicAnim.staggerRecyclerChildren(binding.contentRecyclerView)); - } - - private void setupScreenshotsRecyclerView() { - screenshotsAdapter = new org.chimeramc.client.ui.adapter.ScreenshotsAdapter(new ArrayList(), new org.chimeramc.client.ui.adapter.ScreenshotsAdapter.OnScreenshotClickListener() { - @Override - public void onDeleteClick(org.chimeramc.client.core.content.ScreenshotItem screenshot) { - showDeleteScreenshotDialog(screenshot); - } - - @Override - public void onSaveClick(org.chimeramc.client.core.content.ScreenshotItem screenshot) { - saveScreenshotToGallery(screenshot); - } - - @Override - public void onShareClick(org.chimeramc.client.core.content.ScreenshotItem screenshot) { - org.chimeramc.client.launcher.core.content.ScreenshotShareSheet.shareItem( - ContentListActivity.this, screenshot); - } - }); - binding.contentRecyclerView.setLayoutManager(new androidx.recyclerview.widget.GridLayoutManager(this, 2)); - binding.contentRecyclerView.setAdapter(screenshotsAdapter); - binding.contentRecyclerView.post(() -> DynamicAnim.staggerRecyclerChildren(binding.contentRecyclerView)); - } - - private void setupServersRecyclerView() { - serversAdapter = new org.chimeramc.client.ui.adapter.ServersAdapter( - new ArrayList<>(), - new org.chimeramc.client.ui.adapter.ServersAdapter.OnServerClickListener() { - @Override - public void onDeleteClick(ServerItem server) { - showDeleteServerDialog(server); - } - - @Override - public void onFavouriteToggle(ServerItem server) { - // The star is local presentation state, so the write is a plain - // SharedPreferences apply and the list re-sorts immediately. - favouriteServerKeys = org.chimeramc.client.core.content.ServerFavouritesStore.toggle( - ContentListActivity.this, - org.chimeramc.client.core.content.ServerFavourites.key(server)); - serversAdapter.setFavourites(favouriteServerKeys); - } - }); - GameVersion currentVersion = versionManager.getSelectedVersion(); - serversAdapter.setSelectedVersionCode( - currentVersion == null ? null : currentVersion.versionCode); - serversAdapter.setFavourites(favouriteServerKeys); - binding.contentRecyclerView.setLayoutManager(new LinearLayoutManager(this)); - binding.contentRecyclerView.setAdapter(serversAdapter); - binding.contentRecyclerView.post(() -> DynamicAnim.staggerRecyclerChildren(binding.contentRecyclerView)); - } - - /** - * Pings every listed server on a small background executor and posts the result back. The - * executor is created lazily so the servers tab pays nothing while it is not visible. - */ - private void startServerPings(List servers) { - if (servers == null || servers.isEmpty()) return; - List toPing = new ArrayList<>(); - for (org.chimeramc.client.core.content.ServerItem server : servers) { - String key = org.chimeramc.client.ui.adapter.ServersAdapter.keyFor(server); - if (pingedServerKeys.contains(key)) continue; - toPing.add(server); - pingedServerKeys.add(key); - } - if (toPing.isEmpty()) return; - synchronized (this) { - if (pingExecutor == null) { - pingExecutor = Executors.newFixedThreadPool(3); - } - } - ExecutorService executor = pingExecutor; - for (org.chimeramc.client.core.content.ServerItem server : toPing) { - // A ping for a row the user has just scrolled away from is submitted while the screen is - // still alive, but a refresh can race onDestroy and shut the pool down between the check - // and this submit. The row would then keep saying "checking" forever, so a rejected ping - // is reported as an offline result instead. - try { - executor.execute(() -> { - // The row renders "checking" until a result arrives, so a throw here left that - // server showing a ping check that could never finish. A failure is reported as - // an offline result rather than as no result. - ServerPongInfo pong; - try { - pong = RakNetPing.ping(server.ip, server.port, 3000); - } catch (Throwable t) { - Log.w(TAG, "Server ping failed for " + server.ip + ":" + server.port, t); - pong = null; - } - final ServerPongInfo result = pong; - boolean failed = result == null; - mainHandler.post(() -> { - if (serversAdapter != null) { - serversAdapter.onPingResult(server, result, failed); - } - }); - }); - } catch (java.util.concurrent.RejectedExecutionException rejected) { - mainHandler.post(() -> { - if (serversAdapter != null) { - serversAdapter.onPingResult(server, null, true); - } - }); - } - } - } - - private void setupObservers() { - switch (contentType) { - case TYPE_WORLDS: - contentManager.getWorldsLiveData().observe(this, worlds -> { - allWorlds = worlds != null ? worlds : new ArrayList<>(); - if (worldsAdapter != null) { - filterContent(binding.searchEditText.getText().toString()); - } - showLoading(false); - }); - break; - case TYPE_SKIN_PACKS: - contentManager.getSkinPacksLiveData().observe(this, packs -> { - allPacks = packs != null ? packs : new ArrayList<>(); - if (packsAdapter != null) { - filterContent(binding.searchEditText.getText().toString()); - } - showLoading(false); - }); - break; - case TYPE_RESOURCE_PACKS: - contentManager.getResourcePacksLiveData().observe(this, packs -> { - allPacks = packs != null ? packs : new ArrayList<>(); - if (packsAdapter != null) { - filterContent(binding.searchEditText.getText().toString()); - } - showLoading(false); - }); - break; - case TYPE_BEHAVIOR_PACKS: - contentManager.getBehaviorPacksLiveData().observe(this, packs -> { - allPacks = packs != null ? packs : new ArrayList<>(); - if (packsAdapter != null) { - filterContent(binding.searchEditText.getText().toString()); - } - showLoading(false); - }); - break; - case TYPE_SCREENSHOTS: - contentManager.getScreenshotsLiveData().observe(this, screenshots -> { - if (screenshotsAdapter != null) { - screenshotsAdapter.updateData(screenshots != null ? screenshots : new ArrayList<>()); - } - showLoading(false); - }); - break; - case TYPE_SERVERS: - contentManager.getServersLiveData().observe(this, servers -> { - allServers = servers != null ? servers : new ArrayList<>(); - if (serversAdapter != null) { - filterContent(binding.searchEditText.getText().toString()); - startServerPings(allServers); - } - showLoading(false); - }); - break; - } - } - - private void loadContent() { - showLoading(true); - switch (contentType) { - case TYPE_WORLDS: - contentManager.refreshWorlds(); - break; - case TYPE_SKIN_PACKS: - contentManager.refreshSkinPacks(); - break; - case TYPE_RESOURCE_PACKS: - contentManager.refreshResourcePacks(); - break; - case TYPE_BEHAVIOR_PACKS: - contentManager.refreshBehaviorPacks(); - break; - case TYPE_SCREENSHOTS: - contentManager.refreshScreenshots(); - break; - case TYPE_SERVERS: - contentManager.refreshServers(); - break; - } - } - - private void showLoading(boolean show) { - binding.loadingOverlay.setVisibility(show ? View.VISIBLE : View.GONE); - } - - private void startWorldExport(WorldItem world) { - pendingExportWorld = world; - Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType("application/zip"); - intent.putExtra(Intent.EXTRA_TITLE, world.getName() + ".mcworld"); - exportLauncher.launch(intent); - } - - private void exportWorld(WorldItem world, Uri uri) { - showProgressDialog(getString(R.string.exporting_world)); - contentManager.exportWorld(world, uri, new WorldManager.WorldOperationCallback() { - @Override - public void onSuccess(String message) { - runOnUiThread(() -> { - hideProgressDialog(); - Toast.makeText(ContentListActivity.this, message, Toast.LENGTH_SHORT).show(); - }); - } - - @Override - public void onError(String error) { - runOnUiThread(() -> { - hideProgressDialog(); - Toast.makeText(ContentListActivity.this, error, Toast.LENGTH_LONG).show(); - }); - } - - @Override - public void onProgress(int progress) {} - }); - } - - private void startPackExport(ResourcePackItem pack) { - pendingExportPack = pack; - Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType("application/zip"); - intent.putExtra(Intent.EXTRA_TITLE, pack.getPackName() + ".mcpack"); - exportPackLauncher.launch(intent); - } - - private void exportPack(ResourcePackItem pack, Uri uri) { - showProgressDialog(getString(R.string.exporting_pack)); - contentManager.exportResourcePack(pack, uri, new ResourcePackManager.PackOperationCallback() { - @Override - public void onSuccess(String message) { - runOnUiThread(() -> { - hideProgressDialog(); - Toast.makeText(ContentListActivity.this, message, Toast.LENGTH_SHORT).show(); - }); - } - - @Override - public void onError(String error) { - runOnUiThread(() -> { - hideProgressDialog(); - Toast.makeText(ContentListActivity.this, error, Toast.LENGTH_LONG).show(); - }); - } - - @Override - public void onProgress(int progress) {} - }); - } - - private void backupWorld(WorldItem world) { - showProgressDialog(getString(R.string.backing_up_world)); - contentManager.backupWorld(world, new WorldManager.WorldOperationCallback() { - @Override - public void onSuccess(String message) { - runOnUiThread(() -> { - hideProgressDialog(); - Toast.makeText(ContentListActivity.this, message, Toast.LENGTH_SHORT).show(); - }); - } - - @Override - public void onError(String error) { - runOnUiThread(() -> { - hideProgressDialog(); - Toast.makeText(ContentListActivity.this, error, Toast.LENGTH_LONG).show(); - }); - } - - @Override - public void onProgress(int progress) {} - }); - } - - private void showDeleteWorldDialog(WorldItem world) { - new CustomAlertDialog(this) - .setTitleText(getString(R.string.delete_world)) - .setMessage(getString(R.string.confirm_delete_world)) - .setPositiveButton(getString(R.string.dialog_positive_delete), v -> deleteWorld(world)) - .setNegativeButton(getString(R.string.cancel), null) - .show(); - } - - private void deleteWorld(WorldItem world) { - showProgressDialog(getString(R.string.deleting_world)); - contentManager.deleteWorld(world, new WorldManager.WorldOperationCallback() { - @Override - public void onSuccess(String message) { - runOnUiThread(() -> { - hideProgressDialog(); - Toast.makeText(ContentListActivity.this, message, Toast.LENGTH_SHORT).show(); - }); - } - - @Override - public void onError(String error) { - runOnUiThread(() -> { - hideProgressDialog(); - Toast.makeText(ContentListActivity.this, error, Toast.LENGTH_LONG).show(); - }); - } - - @Override - public void onProgress(int progress) {} - }); - } - - private void showDeletePackDialog(ResourcePackItem pack) { - int titleResId; - int messageResId; - - if (contentType == TYPE_BEHAVIOR_PACKS) { - titleResId = R.string.delete_behavior_pack; - messageResId = R.string.confirm_delete_behavior_pack; - } else if (contentType == TYPE_SKIN_PACKS) { - titleResId = R.string.delete_skin_pack; - messageResId = R.string.confirm_delete_skin_pack; - } else { - titleResId = R.string.delete_resource_pack; - messageResId = R.string.confirm_delete_resource_pack; - } - - new CustomAlertDialog(this) - .setTitleText(getString(titleResId)) - .setMessage(getString(messageResId)) - .setPositiveButton(getString(R.string.dialog_positive_delete), v -> deletePack(pack)) - .setNegativeButton(getString(R.string.cancel), null) - .show(); - } - - private void deletePack(ResourcePackItem pack) { - showProgressDialog(getString(R.string.deleting_pack)); - contentManager.deleteResourcePack(pack, new ResourcePackManager.PackOperationCallback() { - @Override - public void onSuccess(String message) { - runOnUiThread(() -> { - hideProgressDialog(); - Toast.makeText(ContentListActivity.this, message, Toast.LENGTH_SHORT).show(); - }); - } - - @Override - public void onError(String error) { - runOnUiThread(() -> { - hideProgressDialog(); - Toast.makeText(ContentListActivity.this, error, Toast.LENGTH_LONG).show(); - }); - } - - @Override - public void onProgress(int progress) {} - }); - } - - private void saveScreenshotToGallery(org.chimeramc.client.core.content.ScreenshotItem screenshot) { - showLoading(true); - AppExecutors.run(AppExecutors.Kind.DISK, "screenshot-export", () -> { - try { - Bitmap bitmap = BitmapFactory.decodeFile(screenshot.file.getAbsolutePath()); - if (bitmap != null) { - ContentValues values = new ContentValues(); - values.put(MediaStore.Images.Media.DISPLAY_NAME, screenshot.name + "_" + System.currentTimeMillis() + ".jpg"); - values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); - values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000); - - Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); - if (uri != null) { - try (OutputStream out = getContentResolver().openOutputStream(uri)) { - bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out); - } - runOnUiThread(() -> { - showLoading(false); - Toast.makeText(ContentListActivity.this, R.string.saved_to_gallery, Toast.LENGTH_SHORT).show(); - }); - return; - } - } - } catch (Exception e) { - AppLog.warn(TAG, "Opening the content link failed", e); - } - runOnUiThread(() -> { - showLoading(false); - Toast.makeText(ContentListActivity.this, R.string.save_failed, Toast.LENGTH_SHORT).show(); - }); - }); - } - - private void showDeleteScreenshotDialog(org.chimeramc.client.core.content.ScreenshotItem screenshot) { - new CustomAlertDialog(this) - .setTitleText(getString(R.string.delete)) - .setMessage(getString(R.string.delete_screenshot_confirm)) - .setPositiveButton(getString(R.string.dialog_positive_delete), v -> deleteScreenshot(screenshot)) - .setNegativeButton(getString(R.string.cancel), null) - .show(); - } - - private void showAddServerDialog() { - View dialogView = getLayoutInflater().inflate(R.layout.dialog_add_server, null); - EditText serverNameEdit = dialogView.findViewById(R.id.server_name_edit); - EditText serverIpEdit = dialogView.findViewById(R.id.server_ip_edit); - EditText serverPortEdit = dialogView.findViewById(R.id.server_port_edit); - serverPortEdit.setText("19132"); - - new CustomAlertDialog(this) - .setTitleText(getString(R.string.quick_launch_add_server)) - .setCustomView(dialogView) - .setPositiveButton(getString(R.string.add), v -> { - String name = serverNameEdit.getText().toString().trim(); - String ip = serverIpEdit.getText().toString().trim(); - String portStr = serverPortEdit.getText().toString().trim(); - - if (TextUtils.isEmpty(name) || TextUtils.isEmpty(ip)) { - Toast.makeText(this, R.string.server_details_required, Toast.LENGTH_SHORT).show(); - return; - } - - int port = 19132; - if (!TextUtils.isEmpty(portStr)) { - try { - port = Integer.parseInt(portStr); - } catch (NumberFormatException e) { - Toast.makeText(this, R.string.invalid_port, Toast.LENGTH_SHORT).show(); - return; - } - } - - addServer(new org.chimeramc.client.core.content.ServerItem(name, ip, port)); - }) - .setNegativeButton(getString(R.string.cancel), null) - .show(); - } - - private void addServer(org.chimeramc.client.core.content.ServerItem server) { - showProgressDialog(getString(R.string.adding_server)); - contentManager.addServer(server, new ContentManager.ContentOperationCallback() { - @Override - public void onSuccess(String message) { - runOnUiThread(() -> { - hideProgressDialog(); - Toast.makeText(ContentListActivity.this, message, Toast.LENGTH_SHORT).show(); - }); - } - - @Override - public void onError(String error) { - runOnUiThread(() -> { - hideProgressDialog(); - Toast.makeText(ContentListActivity.this, error, Toast.LENGTH_LONG).show(); - }); - } - }); - } - - private void deleteScreenshot(org.chimeramc.client.core.content.ScreenshotItem screenshot) { - showProgressDialog(getString(R.string.deleting_screenshot)); - contentManager.deleteScreenshot(screenshot, new ContentManager.ContentOperationCallback() { - @Override - public void onSuccess(String message) { - runOnUiThread(() -> { - hideProgressDialog(); - Toast.makeText(ContentListActivity.this, message, Toast.LENGTH_SHORT).show(); - }); - } - - @Override - public void onError(String error) { - runOnUiThread(() -> { - hideProgressDialog(); - Toast.makeText(ContentListActivity.this, error, Toast.LENGTH_LONG).show(); - }); - } - }); - } - - private void showDeleteServerDialog(org.chimeramc.client.core.content.ServerItem server) { - new CustomAlertDialog(this) - .setTitleText(getString(R.string.delete)) - .setMessage(getString(R.string.delete_server_confirm)) - .setPositiveButton(getString(R.string.dialog_positive_delete), v -> deleteServer(server)) - .setNegativeButton(getString(R.string.cancel), null) - .show(); - } - - private void deleteServer(org.chimeramc.client.core.content.ServerItem server) { - showProgressDialog(getString(R.string.deleting_server)); - contentManager.deleteServer(server, new ContentManager.ContentOperationCallback() { - @Override - public void onSuccess(String message) { - runOnUiThread(() -> { - hideProgressDialog(); - Toast.makeText(ContentListActivity.this, message, Toast.LENGTH_SHORT).show(); - }); - } - - @Override - public void onError(String error) { - runOnUiThread(() -> { - hideProgressDialog(); - Toast.makeText(ContentListActivity.this, error, Toast.LENGTH_LONG).show(); - }); - } - }); - } - - private void openCustomFlatWorld() { - if (worldsDirectory == null || !worldsDirectory.exists()) { - Toast.makeText(this, R.string.worlds_directory_unavailable, Toast.LENGTH_SHORT).show(); - return; - } - - Intent intent = new Intent(this, CustomFlatWorldActivity.class); - intent.putExtra(CustomFlatWorldActivity.EXTRA_WORLDS_DIRECTORY, worldsDirectory.getAbsolutePath()); - customFlatWorldLauncher.launch(intent); - } - - private void showExtractStructuresDialog(WorldItem world) { - File worldFile = world.getFile(); - if (worldFile == null || !worldFile.exists()) { - Toast.makeText(this, R.string.world_directory_not_found, Toast.LENGTH_SHORT).show(); - return; - } - - binding.loadingOverlay.setVisibility(View.VISIBLE); - - structureExtractor.loadStructures(worldFile, new StructureExtractor.StructureListCallback() { - @Override - public void onComplete(List structures) { - runOnUiThread(() -> { - binding.loadingOverlay.setVisibility(View.GONE); - if (structures.isEmpty()) { - showNoStructuresFoundDialog(); - } else { - showStructureSelectionDialog(world, structures); - } - }); - } - - @Override - public void onError(String error) { - runOnUiThread(() -> { - binding.loadingOverlay.setVisibility(View.GONE); - Toast.makeText(ContentListActivity.this, error, Toast.LENGTH_LONG).show(); - }); - } - }); - } - - private void showNoStructuresFoundDialog() { - new CustomAlertDialog(this) - .setTitleText(getString(R.string.no_structures_found_title)) - .setMessage(getString(R.string.no_structures_found_message)) - .setPositiveButton(getString(R.string.dialog_positive_ok), null) - .show(); - } - - private void showStructureSelectionDialog(WorldItem world, List structures) { - View dialogView = getLayoutInflater().inflate(R.layout.dialog_structure_list, null); - - TextView structureCount = dialogView.findViewById(R.id.structure_count); - RecyclerView recyclerView = dialogView.findViewById(R.id.structures_recycler_view); - - structureCount.setText(getResources().getQuantityString(R.plurals.structures_found_count, structures.size(), structures.size())); - - StructuresAdapter adapter = new StructuresAdapter(); - adapter.setStructures(structures); - - recyclerView.setLayoutManager(new LinearLayoutManager(this)); - recyclerView.setAdapter(adapter); - - AlertDialog dialog = new MaterialAlertDialogBuilder(this) - .setTitle(R.string.structures_found_title) - .setView(dialogView) - .setNegativeButton(R.string.cancel, null) - .create(); - - adapter.setOnStructureExportListener(structure -> { - dialog.dismiss(); - startStructureExport(world, structure); - }); - - dialog.show(); - - org.chimeramc.client.util.PersonalizationManager structPm = new org.chimeramc.client.util.PersonalizationManager(this); - int structAccent = structPm.getAccentColor(); - if (structAccent != 0) { - dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(structAccent); - } - } - - private void startStructureExport(WorldItem world, StructureExtractor.StructureInfo structure) { - pendingStructureExportWorld = world; - pendingStructureInfo = structure; - - Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType("application/octet-stream"); - intent.putExtra(Intent.EXTRA_TITLE, structure.getFileName()); - structureExportLauncher.launch(intent); - } - - private void exportStructureToFile(WorldItem world, StructureExtractor.StructureInfo structure, Uri uri) { - binding.loadingOverlay.setVisibility(View.VISIBLE); - - structureExtractor.exportSingleStructure(structure, uri, new StructureExtractor.ExtractionCallback() { - - @Override - public void onComplete(int extractedCount, String outputPath) { - runOnUiThread(() -> { - binding.loadingOverlay.setVisibility(View.GONE); - Toast.makeText(ContentListActivity.this, - getString(R.string.structure_exported, structure.getName()), - Toast.LENGTH_SHORT).show(); - }); - } - - @Override - public void onError(String error) { - runOnUiThread(() -> { - binding.loadingOverlay.setVisibility(View.GONE); - Toast.makeText(ContentListActivity.this, error, Toast.LENGTH_LONG).show(); - }); - } - }); - } - - private void showTransferWorldDialog(WorldItem world) { - View dialogView = getLayoutInflater().inflate(R.layout.dialog_transfer_content, null); - RadioGroup radioGroup = dialogView.findViewById(R.id.storage_radio_group); - RadioButton radioInternal = dialogView.findViewById(R.id.radio_internal); - RadioButton radioExternal = dialogView.findViewById(R.id.radio_external); - RadioButton radioVersionIsolationInternal = dialogView.findViewById(R.id.radio_version_isolation_internal); - RadioButton radioVersionIsolation = dialogView.findViewById(R.id.radio_version_isolation); - radioVersionIsolationInternal.setText(getString(R.string.storage_version_isolation) + " (" + getString(R.string.storage_internal) + ")"); - radioVersionIsolation.setText(getString(R.string.storage_version_isolation) + " (" + getString(R.string.storage_external) + ")"); - - switch (currentStorageType) { - case INTERNAL -> radioInternal.setEnabled(false); - case EXTERNAL -> radioExternal.setEnabled(false); - case VERSION_ISOLATION_INTERNAL -> radioVersionIsolationInternal.setEnabled(false); - case VERSION_ISOLATION, VERSION_ISOLATION_EXTERNAL -> radioVersionIsolation.setEnabled(false); - } - - AlertDialog dialog = new MaterialAlertDialogBuilder(this) - .setTitle(R.string.transfer_content) - .setView(dialogView) - .setPositiveButton(R.string.transfer, (d, which) -> { - int selectedId = radioGroup.getCheckedRadioButtonId(); - FeatureSettings.StorageType targetType = null; - - if (selectedId == R.id.radio_internal) { - targetType = FeatureSettings.StorageType.INTERNAL; - } else if (selectedId == R.id.radio_external) { - targetType = FeatureSettings.StorageType.EXTERNAL; - } else if (selectedId == R.id.radio_version_isolation_internal) { - targetType = FeatureSettings.StorageType.VERSION_ISOLATION_INTERNAL; - } else if (selectedId == R.id.radio_version_isolation) { - targetType = FeatureSettings.StorageType.VERSION_ISOLATION_EXTERNAL; - } - - if (targetType != null && targetType != currentStorageType) { - transferWorld(world, targetType); - } - }) - .setNegativeButton(R.string.cancel, null) - .show(); - - org.chimeramc.client.util.PersonalizationManager twPm = new org.chimeramc.client.util.PersonalizationManager(this); - int twAccent = twPm.getAccentColor(); - if (twAccent != 0) { - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(twAccent); - dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(twAccent); - } else { - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(getResources().getColor(R.color.accent_text, getTheme())); - dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(getResources().getColor(R.color.accent_text, getTheme())); - } - } - - private void showTransferPackDialog(ResourcePackItem pack) { - View dialogView = getLayoutInflater().inflate(R.layout.dialog_transfer_content, null); - RadioGroup radioGroup = dialogView.findViewById(R.id.storage_radio_group); - RadioButton radioInternal = dialogView.findViewById(R.id.radio_internal); - RadioButton radioExternal = dialogView.findViewById(R.id.radio_external); - RadioButton radioVersionIsolationInternal = dialogView.findViewById(R.id.radio_version_isolation_internal); - RadioButton radioVersionIsolation = dialogView.findViewById(R.id.radio_version_isolation); - radioVersionIsolationInternal.setText(getString(R.string.storage_version_isolation) + " (" + getString(R.string.storage_internal) + ")"); - radioVersionIsolation.setText(getString(R.string.storage_version_isolation) + " (" + getString(R.string.storage_external) + ")"); - - switch (currentStorageType) { - case INTERNAL -> radioInternal.setEnabled(false); - case EXTERNAL -> radioExternal.setEnabled(false); - case VERSION_ISOLATION_INTERNAL -> radioVersionIsolationInternal.setEnabled(false); - case VERSION_ISOLATION, VERSION_ISOLATION_EXTERNAL -> radioVersionIsolation.setEnabled(false); - } - - AlertDialog dialog = new MaterialAlertDialogBuilder(this) - .setTitle(R.string.transfer_content) - .setView(dialogView) - .setPositiveButton(R.string.transfer, (d, which) -> { - int selectedId = radioGroup.getCheckedRadioButtonId(); - FeatureSettings.StorageType targetType = null; - - if (selectedId == R.id.radio_internal) { - targetType = FeatureSettings.StorageType.INTERNAL; - } else if (selectedId == R.id.radio_external) { - targetType = FeatureSettings.StorageType.EXTERNAL; - } else if (selectedId == R.id.radio_version_isolation_internal) { - targetType = FeatureSettings.StorageType.VERSION_ISOLATION_INTERNAL; - } else if (selectedId == R.id.radio_version_isolation) { - targetType = FeatureSettings.StorageType.VERSION_ISOLATION_EXTERNAL; - } - - if (targetType != null && targetType != currentStorageType) { - transferPack(pack, targetType); - } - }) - .setNegativeButton(R.string.cancel, null) - .show(); - - org.chimeramc.client.util.PersonalizationManager tpPm = new org.chimeramc.client.util.PersonalizationManager(this); - int tpAccent = tpPm.getAccentColor(); - if (tpAccent != 0) { - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(tpAccent); - dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(tpAccent); - } else { - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(getResources().getColor(R.color.accent_text, getTheme())); - dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(getResources().getColor(R.color.accent_text, getTheme())); - } - } - - private void transferWorld(WorldItem world, FeatureSettings.StorageType targetType) { - File targetDir = getWorldsDirectoryForType(targetType); - if (targetDir == null) { - Toast.makeText(this, getString(R.string.transfer_failed), Toast.LENGTH_SHORT).show(); - return; - } - - showLoading(true); - contentManager.transferWorld(world, targetDir, new WorldManager.WorldOperationCallback() { - @Override - public void onSuccess(String message) { - runOnUiThread(() -> { - showLoading(false); - Toast.makeText(ContentListActivity.this, getString(R.string.transfer_success), Toast.LENGTH_SHORT).show(); - loadContent(); - }); - } - - @Override - public void onError(String error) { - runOnUiThread(() -> { - showLoading(false); - Toast.makeText(ContentListActivity.this, error, Toast.LENGTH_LONG).show(); - }); - } - - @Override - public void onProgress(int progress) {} - }); - } - - private void transferPack(ResourcePackItem pack, FeatureSettings.StorageType targetType) { - File targetDir = getPackDirectoryForType(targetType, pack.isBehaviorPack() ? "behavior_packs" : - (contentType == TYPE_SKIN_PACKS ? "skin_packs" : "resource_packs")); - if (targetDir == null) { - Toast.makeText(this, getString(R.string.transfer_failed), Toast.LENGTH_SHORT).show(); - return; - } - - showLoading(true); - contentManager.transferResourcePack(pack, targetDir, new ResourcePackManager.PackOperationCallback() { - @Override - public void onSuccess(String message) { - runOnUiThread(() -> { - showLoading(false); - Toast.makeText(ContentListActivity.this, getString(R.string.transfer_success), Toast.LENGTH_SHORT).show(); - loadContent(); - }); - } - - @Override - public void onError(String error) { - runOnUiThread(() -> { - showLoading(false); - Toast.makeText(ContentListActivity.this, error, Toast.LENGTH_LONG).show(); - }); - } - - @Override - public void onProgress(int progress) {} - }); - } - - private File getWorldsDirectoryForType(FeatureSettings.StorageType storageType) { - File gameDataDir = getGameDataDirForType(storageType); - return gameDataDir == null ? null : new File(gameDataDir, "minecraftWorlds"); - } - - private File getPackDirectoryForType(FeatureSettings.StorageType storageType, String packType) { - File gameDataDir = getGameDataDirForType(storageType); - return gameDataDir == null ? null : new File(gameDataDir, packType); - } - - private File getGameDataDirForType(FeatureSettings.StorageType storageType) { - GameVersion currentVersion = versionManager.getSelectedVersion(); - // With no instance selected there is still a place the game keeps its servers, and that is - // the shared root. Returning null here meant ServerManager was handed a null directory and - // every "add server" reported failure until an instance happened to be selected — the - // "servers don't work" report. The shared resolution needs no profile, so it is a sound - // fallback rather than a guess. - if (currentVersion == null) { - return LauncherStorage.getContentGameDataDir( - this, - LauncherStorage.INSTALLED_MINECRAFT_PROFILE_ID, - LauncherStorage.normalizeContentStorageType(storageType, false) - ); - } - FeatureSettings.StorageType resolvedType = LauncherStorage.normalizeContentStorageType( - storageType, - currentVersion.versionIsolation - ); - return LauncherStorage.getContentGameDataDir(this, currentVersion.getStorageProfileId(), resolvedType); - } - - private FeatureSettings.StorageType parseStorageType(String value) { - try { - return FeatureSettings.StorageType.valueOf(value); - } catch (Exception ignored) { - return FeatureSettings.StorageType.INTERNAL; - } - } - - @Override - protected void onResume() { - super.onResume(); - loadContent(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (structureExtractor != null) { - structureExtractor.shutdown(); - } - if (pingExecutor != null) { - pingExecutor.shutdownNow(); - pingExecutor = null; - } - // The screenshots adapter owns a decode pool; without this its four worker threads outlive - // the screen and stay parked for the life of the process. - if (screenshotsAdapter != null) { - screenshotsAdapter.shutdown(); - screenshotsAdapter = null; - } - // The pack adapter owns a background size/resolution scanner; it must be - // stopped with the screen or its worker outlives the activity. - if (packsAdapter != null) { - packsAdapter.dispose(); - packsAdapter = null; - } - // A dialog still attached to a destroyed Activity is the "Activity has leaked window" - // crash; it also trapped the user, because nothing could dismiss it once the owner was gone. - hideProgressDialog(); - } -} diff --git a/app/src/main/java/org/chimeramc/client/launcher/ui/activities/ContentManagementActivity.java b/app/src/main/java/org/chimeramc/client/launcher/ui/activities/ContentManagementActivity.java deleted file mode 100644 index f3929a69..00000000 --- a/app/src/main/java/org/chimeramc/client/launcher/ui/activities/ContentManagementActivity.java +++ /dev/null @@ -1,368 +0,0 @@ -package org.chimeramc.client.ui.activities; - -import android.content.Intent; -import android.content.SharedPreferences; -import android.net.Uri; -import android.os.Bundle; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.Toast; - -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; - -import org.chimeramc.client.R; -import org.chimeramc.client.core.content.ContentImporter; -import org.chimeramc.client.core.content.ContentManager; -import org.chimeramc.client.core.versions.GameVersion; -import org.chimeramc.client.core.versions.VersionManager; -import org.chimeramc.client.databinding.ActivityContentManagementBinding; -import org.chimeramc.client.settings.FeatureSettings; -import org.chimeramc.client.ui.animation.DynamicAnim; -import org.chimeramc.client.util.LauncherStorage; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -public class ContentManagementActivity extends BaseActivity { - - private static final String PREFS_NAME = "content_management"; - private static final String KEY_STORAGE_TYPE = "storage_type"; - - private ActivityContentManagementBinding binding; - private ContentManager contentManager; - private ContentImporter contentImporter; - private VersionManager versionManager; - private FeatureSettings.StorageType currentStorageType = FeatureSettings.StorageType.INTERNAL; - private SharedPreferences prefs; - private ActivityResultLauncher importLauncher; - private org.chimeramc.client.ui.dialogs.LoadingDialog progressDialog; - - private void showProgressDialog(String message) { - // Showing a dialog on a finishing Activity throws WindowManager.BadTokenException, which - // took the screen down instead of reporting whatever it was about to report. The operation - // still runs; only the dialog is skipped. - if (isFinishing() || isDestroyed()) { - return; - } - if (progressDialog == null) { - progressDialog = new org.chimeramc.client.ui.dialogs.LoadingDialog(this); - } - progressDialog.show(); - progressDialog.setMessage(message); - } - - private void hideProgressDialog() { - if (progressDialog != null && progressDialog.isShowing()) { - progressDialog.dismiss(); - } - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - binding = ActivityContentManagementBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - DynamicAnim.applyPressScaleRecursively(binding.getRoot()); - - setupActivityResultLaunchers(); - initializeManagers(); - setupUI(); - loadCurrentVersion(); - } - - private void setupActivityResultLaunchers() { - importLauncher = registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), - result -> { - if (result.getResultCode() == RESULT_OK && result.getData() != null) { - Intent data = result.getData(); - List uris = new ArrayList<>(); - if (data.getClipData() != null) { - int count = data.getClipData().getItemCount(); - for (int i = 0; i < count; i++) { - uris.add(data.getClipData().getItemAt(i).getUri()); - } - } else if (data.getData() != null) { - uris.add(data.getData()); - } - if (!uris.isEmpty()) { - handleImport(uris); - } - } - } - ); - } - - private void initializeManagers() { - contentManager = ContentManager.getInstance(this); - contentImporter = new ContentImporter(this); - versionManager = VersionManager.get(this); - prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); - loadStorageType(); - } - - private void loadStorageType() { - String savedType = prefs.getString(KEY_STORAGE_TYPE, "INTERNAL"); - currentStorageType = parseStorageType(savedType); - normalizeCurrentStorageType(); - } - - private void saveStorageType() { - prefs.edit().putString(KEY_STORAGE_TYPE, currentStorageType.name()).apply(); - } - - private void setupUI() { - binding.editOptionsButton.setOnClickListener(v -> openOptionsEditor()); - binding.importContentButton.setOnClickListener(v -> startImport()); - - setupStorageSpinner(); - setupCategoryButtons(); - } - - private void startImport() { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("*/*"); - intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"application/zip", "application/octet-stream"}); - intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); - importLauncher.launch(Intent.createChooser(intent, getString(R.string.import_content_title))); - } - - private void handleImport(List uris) { - GameVersion currentVersion = versionManager.getSelectedVersion(); - if (currentVersion == null) { - Toast.makeText(this, R.string.not_found_version, Toast.LENGTH_SHORT).show(); - return; - } - - File worldsDir = getWorldsDirectory(); - File resourcePacksDir = getPackDirectory("resource_packs"); - File behaviorPacksDir = getPackDirectory("behavior_packs"); - File skinPacksDir = getPackDirectory("skin_packs"); - - showProgressDialog(getString(R.string.importing_content)); - - contentImporter.importContent(uris, resourcePacksDir, behaviorPacksDir, skinPacksDir, worldsDir, - new ContentImporter.ImportCallback() { - @Override - public void onSuccess(String message) { - runOnUiThread(() -> { - hideProgressDialog(); - Toast.makeText(ContentManagementActivity.this, message, Toast.LENGTH_SHORT).show(); - contentManager.refreshContent(); - }); - } - - @Override - public void onError(String error) { - runOnUiThread(() -> { - hideProgressDialog(); - Toast.makeText(ContentManagementActivity.this, error, Toast.LENGTH_LONG).show(); - }); - } - - @Override - public void onProgress(int progress) { - } - }); - } - - private File getPackDirectory(String packType) { - File gameDataDir = getGameDataDirForType(currentStorageType); - return gameDataDir == null ? null : new File(gameDataDir, packType); - } - - private void setupStorageSpinner() { - String[] storageOptions = { - getString(R.string.storage_internal), - getString(R.string.storage_external), - getString(R.string.storage_version_isolation) + " (" + getString(R.string.storage_internal) + ")", - getString(R.string.storage_version_isolation) + " (" + getString(R.string.storage_external) + ")" - }; - - ArrayAdapter adapter = new ArrayAdapter<>(this, R.layout.spinner_item, storageOptions); - adapter.setDropDownViewResource(R.layout.spinner_dropdown_item); - binding.storageTypeSpinner.setAdapter(adapter); - binding.storageTypeSpinner.setPopupBackgroundResource(R.drawable.bg_popup_menu_rounded); - DynamicAnim.applyPressScale(binding.storageTypeSpinner); - - int currentSelection = switch (currentStorageType) { - case INTERNAL -> 0; - case EXTERNAL -> 1; - case VERSION_ISOLATION_INTERNAL -> 2; - case VERSION_ISOLATION, VERSION_ISOLATION_EXTERNAL -> 3; - }; - binding.storageTypeSpinner.setSelection(currentSelection); - - binding.storageTypeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - FeatureSettings.StorageType newType = switch (position) { - case 0 -> FeatureSettings.StorageType.INTERNAL; - case 1 -> FeatureSettings.StorageType.EXTERNAL; - case 2 -> FeatureSettings.StorageType.VERSION_ISOLATION_INTERNAL; - case 3 -> FeatureSettings.StorageType.VERSION_ISOLATION_EXTERNAL; - default -> FeatureSettings.StorageType.VERSION_ISOLATION_EXTERNAL; - }; - - if (newType != currentStorageType) { - currentStorageType = newType; - saveStorageType(); - updateStorageDirectories(); - } - } - - @Override - public void onNothingSelected(AdapterView parent) {} - }); - } - - private void setupCategoryButtons() { - binding.worldsButton.setOnClickListener(v -> openContentList(ContentListActivity.TYPE_WORLDS)); - binding.skinPacksButton.setOnClickListener(v -> openContentList(ContentListActivity.TYPE_SKIN_PACKS)); - binding.resourcePacksButton.setOnClickListener(v -> openContentList(ContentListActivity.TYPE_RESOURCE_PACKS)); - binding.behaviorPacksButton.setOnClickListener(v -> openContentList(ContentListActivity.TYPE_BEHAVIOR_PACKS)); - binding.screenshotsButton.setOnClickListener(v -> openContentList(ContentListActivity.TYPE_SCREENSHOTS)); - binding.serversButton.setOnClickListener(v -> openContentList(ContentListActivity.TYPE_SERVERS)); - // Guest mode removes the server list: a borrowed device should not show which - // servers the owner plays on, and this is the only entry to that category. - if (org.chimeramc.client.core.privacy.PrivacyModes.current(this).hidesServers()) { - binding.serversButton.setVisibility(View.GONE); - } - } - - private void openContentList(int contentType) { - Intent intent = new Intent(this, ContentListActivity.class); - intent.putExtra(ContentListActivity.EXTRA_CONTENT_TYPE, contentType); - intent.putExtra(ContentListActivity.EXTRA_CURRENT_STORAGE_TYPE, currentStorageType.name()); - - if (contentType == ContentListActivity.TYPE_WORLDS) { - File worldsDir = getWorldsDirectory(); - if (worldsDir != null) { - intent.putExtra(ContentListActivity.EXTRA_WORLDS_DIRECTORY, worldsDir.getAbsolutePath()); - } - } - - startActivity(intent); - } - - private File getWorldsDirectory() { - File gameDataDir = getGameDataDirForType(currentStorageType); - return gameDataDir == null ? null : new File(gameDataDir, "minecraftWorlds"); - } - - private void loadCurrentVersion() { - GameVersion currentVersion = versionManager.getSelectedVersion(); - if (currentVersion != null) { - binding.versionText.setText(currentVersion.displayName); - updateStorageDirectories(); - } else { - binding.versionText.setText(getString(R.string.not_found_version)); - Toast.makeText(this, R.string.dialog_title_no_version, Toast.LENGTH_SHORT).show(); - } - } - - private void updateStorageDirectories() { - GameVersion currentVersion = versionManager.getSelectedVersion(); - if (currentVersion == null) return; - - File worldsDir; - File resourcePacksDir; - File behaviorPacksDir; - File skinPacksDir; - File screenshotsDir; - File minecraftPeDir; - - File gameDataDir = getGameDataDirForType(currentStorageType); - if (gameDataDir == null) { - worldsDir = null; - resourcePacksDir = null; - behaviorPacksDir = null; - skinPacksDir = null; - screenshotsDir = null; - minecraftPeDir = null; - } else { - worldsDir = new File(gameDataDir, "minecraftWorlds"); - resourcePacksDir = new File(gameDataDir, "resource_packs"); - behaviorPacksDir = new File(gameDataDir, "behavior_packs"); - skinPacksDir = new File(gameDataDir, "skin_packs"); - screenshotsDir = new File(gameDataDir, "Screenshots"); - minecraftPeDir = new File(gameDataDir, "minecraftpe"); - } - - contentManager.setStorageDirectories(worldsDir, resourcePacksDir, behaviorPacksDir, skinPacksDir, screenshotsDir, minecraftPeDir); - } - - private void openOptionsEditor() { - File optionsFile = getOptionsFile(); - if (optionsFile == null) { - Toast.makeText(this, R.string.options_file_not_found, Toast.LENGTH_SHORT).show(); - return; - } - - String storageLabel = switch (currentStorageType) { - case INTERNAL -> getString(R.string.storage_internal); - case EXTERNAL -> getString(R.string.storage_external); - case VERSION_ISOLATION_INTERNAL -> getString(R.string.storage_version_isolation) + " (" + getString(R.string.storage_internal) + ")"; - case VERSION_ISOLATION, VERSION_ISOLATION_EXTERNAL -> getString(R.string.storage_version_isolation) + " (" + getString(R.string.storage_external) + ")"; - }; - - Intent intent = new Intent(this, OptionsEditorActivity.class); - intent.putExtra(OptionsEditorActivity.EXTRA_OPTIONS_PATH, optionsFile.getAbsolutePath()); - intent.putExtra(OptionsEditorActivity.EXTRA_STORAGE_TYPE, storageLabel); - startActivity(intent); - } - - private File getOptionsFile() { - GameVersion currentVersion = versionManager.getSelectedVersion(); - - File gameDataDir = getGameDataDirForType(currentStorageType); - return gameDataDir == null ? null : new File(gameDataDir, "minecraftpe/options.txt"); - } - - private File getGameDataDirForType(FeatureSettings.StorageType storageType) { - GameVersion currentVersion = versionManager.getSelectedVersion(); - if (currentVersion == null) return null; - FeatureSettings.StorageType resolvedType = LauncherStorage.normalizeContentStorageType( - storageType, - currentVersion.versionIsolation - ); - return LauncherStorage.getContentGameDataDir(this, currentVersion.getStorageProfileId(), resolvedType); - } - - private void normalizeCurrentStorageType() { - GameVersion currentVersion = versionManager != null ? versionManager.getSelectedVersion() : null; - if (currentVersion == null) return; - currentStorageType = LauncherStorage.normalizeContentStorageType( - currentStorageType, - currentVersion.versionIsolation - ); - } - - private FeatureSettings.StorageType parseStorageType(String value) { - try { - return FeatureSettings.StorageType.valueOf(value); - } catch (Exception ignored) { - return FeatureSettings.StorageType.INTERNAL; - } - } - - @Override - protected void onResume() { - super.onResume(); - updateStorageDirectories(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (contentImporter != null) { - contentImporter.shutdown(); - } - // A dialog still attached to a destroyed Activity is the "Activity has leaked window" - // crash, and it trapped the user because nothing could dismiss it once the owner was gone. - hideProgressDialog(); - } -} diff --git a/app/src/main/java/org/chimeramc/client/launcher/ui/activities/CrashActivity.java b/app/src/main/java/org/chimeramc/client/launcher/ui/activities/CrashActivity.java new file mode 100644 index 00000000..fcee8894 --- /dev/null +++ b/app/src/main/java/org/chimeramc/client/launcher/ui/activities/CrashActivity.java @@ -0,0 +1,20 @@ +package org.chimeramc.client.ui.activities; + +import android.os.Bundle; + +import androidx.annotation.Nullable; + +/** + * Placeholder screen. + * + *

The legacy UI was removed in one sweep so a new one can be dropped in. This class survives + * only because non-UI code (the manifest, an Intent target, or a nav destination) still names it; + * it draws nothing. Replace it with the real CrashActivity screen. + */ +public class CrashActivity extends BaseActivity { + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } +} diff --git a/app/src/main/java/org/chimeramc/client/launcher/ui/activities/CrashActivity.kt b/app/src/main/java/org/chimeramc/client/launcher/ui/activities/CrashActivity.kt deleted file mode 100644 index a16f8f0c..00000000 --- a/app/src/main/java/org/chimeramc/client/launcher/ui/activities/CrashActivity.kt +++ /dev/null @@ -1,320 +0,0 @@ -package org.chimeramc.client.ui.activities - -import org.chimeramc.client.util.AppLog - -import android.content.Intent -import android.os.Bundle -import android.os.Handler -import android.os.Looper -import android.widget.Button -import android.widget.TextView -import android.widget.Toast -import androidx.core.content.FileProvider -import org.chimeramc.client.R -import org.chimeramc.client.core.crash.CrashReporter -import org.chimeramc.client.core.crash.ProcessExitReasons -import org.chimeramc.client.core.mods.ModManager -import org.chimeramc.client.core.mods.ModSafeMode -import org.chimeramc.client.core.versions.GameVersion -import org.chimeramc.client.core.versions.VersionManager -import org.chimeramc.client.launcher.core.minecraft.LaunchErrorDiagnosis -import org.chimeramc.client.ui.animation.DynamicAnim -import java.io.File -import java.util.concurrent.Executors - -class CrashActivity : BaseActivity() { - - private var executor = Executors.newSingleThreadExecutor() - private var logPath: String? = null - private var disableButton: Button? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContentView(R.layout.activity_crash) - - logPath = intent.getStringExtra("LOG_PATH") - val summary = intent.getStringExtra("SUMMARY") - ?: intent.getStringExtra("EMERGENCY") - val crashType = intent.getStringExtra("CRASH_TYPE") - // A crash screen means the previous process died before a session was recorded, so - // count it here rather than from the dying process -- an async write from a crashing process - // is not guaranteed to land. This screen runs in the fresh launcher process, where the - // write is durable. - org.chimeramc.client.core.stats.LaunchStatsRecorder.recordCrash(this) - CrashReporter.sendUnsentReports(this) - - val tvTitle = findViewById(R.id.crash_title) - val tvDetails = findViewById(R.id.crash_details) - val btnBack = findViewById