From 852e7ef69bc83359cd32d4548329dd4d470f4781 Mon Sep 17 00:00:00 2001 From: harshsomankar123-tech Date: Fri, 27 Mar 2026 16:14:24 +0530 Subject: [PATCH 01/18] IDE-309 Fix FormulaEditorUndoTest NPE in checkVariables Add null guards in ScriptFragment.checkVariables() for saved variable lists and null check for fragment in FormulaEditorFragment.exitFormulaEditorFragment() to prevent NullPointerException when no undo state has been captured. --- .../catrobat/catroid/ui/fragment/FormulaEditorFragment.java | 2 +- .../catroid/ui/recyclerview/fragment/ScriptFragment.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/fragment/FormulaEditorFragment.java b/catroid/src/main/java/org/catrobat/catroid/ui/fragment/FormulaEditorFragment.java index f597fbbd29c..ad2765ee517 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/fragment/FormulaEditorFragment.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/fragment/FormulaEditorFragment.java @@ -932,7 +932,7 @@ public void exitFormulaEditorFragment() { XstreamSerializer.getInstance().saveProject(ProjectManager.getInstance().getCurrentProject()); - if (hasFileChanged() || fragment.checkVariables()) { + if (hasFileChanged() || (fragment != null && fragment.checkVariables())) { ((SpriteActivity) getActivity()).setUndoMenuItemVisibility(true); } } diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java index 0cdce4084e3..0dccafac7a9 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java @@ -936,6 +936,11 @@ private void saveVariables() { } public boolean checkVariables() { + if (savedUserVariables == null || savedMultiplayerVariables == null + || savedUserLists == null || savedLocalUserVariables == null + || savedLocalLists == null) { + return false; + } ProjectManager projectManager = ProjectManager.getInstance(); Sprite currentSprite = projectManager.getCurrentSprite(); Project project = projectManager.getCurrentProject(); From d2189fcb978db07aa58332da8ba7371fb739f9f3 Mon Sep 17 00:00:00 2001 From: harshsomankar123-tech Date: Fri, 27 Mar 2026 23:17:29 +0530 Subject: [PATCH 02/18] Refine null handling in checkVariables and add regression test --- .../formulaeditor/FormulaEditorUndoTest.java | 50 +++++++++++++++++++ .../recyclerview/fragment/ScriptFragment.java | 36 +++++++++---- 2 files changed, 75 insertions(+), 11 deletions(-) diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.java index f3262497385..32203486c7f 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.java @@ -44,6 +44,7 @@ import org.junit.runner.RunWith; import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; import static org.catrobat.catroid.uiespresso.content.brick.utils.BrickDataInteractionWrapper.onBrickAtPosition; import static org.catrobat.catroid.uiespresso.content.brick.utils.ColorPickerInteractionWrapper.onColorPickerPresetButton; @@ -435,4 +436,53 @@ public void testUndoFormulaWithColorPicker() { .onFormulaTextField(R.id.brick_place_at_edit_text_y) .checkShowsNumber(0); } + + @Category({Cat.AppUi.class, Level.Smoke.class}) + @Test + public void testUndoAfterActivityRecreationDoesNotCrash() { + onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at); + + onView(withId(R.id.brick_place_at_edit_text_x)) + .perform(click()); + onView(withText(R.string.brick_context_dialog_formula_edit_brick)) + .perform(click()); + + onFormulaEditor() + .performOpenDataFragment(); + + onDataList() + .performAdd(NEW_VARIABLE_NAME); + + onDataList() + .performClose(); + + pressBack(); + + onView(withId(R.id.menu_undo)) + .check(matches(isDisplayed())); + + InstrumentationRegistry.getInstrumentation().runOnMainSync( + () -> baseActivityTestRule.getActivity().recreate()); + + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + + onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at); + + onView(withId(R.id.brick_place_at_edit_text_x)) + .perform(click()); + onView(withText(R.string.brick_context_dialog_formula_edit_brick)) + .perform(click()); + + onFormulaEditor() + .performEnterFormula("999"); + + pressBack(); + + onView(withId(R.id.menu_undo)) + .check(matches(isDisplayed())); + + onBrickAtPosition(brickPosition) + .onFormulaTextField(R.id.brick_place_at_edit_text_x) + .checkShowsNumber(999); + } } diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java index 0dccafac7a9..36a47d4f65d 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java @@ -936,16 +936,20 @@ private void saveVariables() { } public boolean checkVariables() { - if (savedUserVariables == null || savedMultiplayerVariables == null - || savedUserLists == null || savedLocalUserVariables == null - || savedLocalLists == null) { - return false; - } ProjectManager projectManager = ProjectManager.getInstance(); Sprite currentSprite = projectManager.getCurrentSprite(); Project project = projectManager.getCurrentProject(); - return (project.hasUserDataChanged(project.getUserVariables(), savedUserVariables) || project.hasUserDataChanged(project.getMultiplayerVariables(), savedMultiplayerVariables) || project.hasUserDataChanged(project.getUserLists(), savedUserLists) || currentSprite.hasUserDataChanged(currentSprite.getUserVariables(), savedLocalUserVariables) || currentSprite.hasUserDataChanged(currentSprite.getUserLists(), savedLocalLists)); + return (savedUserVariables != null + && project.hasUserDataChanged(project.getUserVariables(), savedUserVariables)) + || (savedMultiplayerVariables != null + && project.hasUserDataChanged(project.getMultiplayerVariables(), savedMultiplayerVariables)) + || (savedUserLists != null + && project.hasUserDataChanged(project.getUserLists(), savedUserLists)) + || (savedLocalUserVariables != null + && currentSprite.hasUserDataChanged(currentSprite.getUserVariables(), savedLocalUserVariables)) + || (savedLocalLists != null + && currentSprite.hasUserDataChanged(currentSprite.getUserLists(), savedLocalLists)); } private void loadVariables() { @@ -953,11 +957,21 @@ private void loadVariables() { Sprite currentSprite = projectManager.getCurrentSprite(); Project project = projectManager.getCurrentProject(); - project.restoreUserDataValues(project.getUserVariables(), savedUserVariables); - project.restoreUserDataValues(project.getMultiplayerVariables(), savedMultiplayerVariables); - project.restoreUserDataValues(project.getUserLists(), savedUserLists); - currentSprite.restoreUserDataValues(currentSprite.getUserVariables(), savedLocalUserVariables); - currentSprite.restoreUserDataValues(currentSprite.getUserLists(), savedLocalLists); + if (savedUserVariables != null) { + project.restoreUserDataValues(project.getUserVariables(), savedUserVariables); + } + if (savedMultiplayerVariables != null) { + project.restoreUserDataValues(project.getMultiplayerVariables(), savedMultiplayerVariables); + } + if (savedUserLists != null) { + project.restoreUserDataValues(project.getUserLists(), savedUserLists); + } + if (savedLocalUserVariables != null) { + currentSprite.restoreUserDataValues(currentSprite.getUserVariables(), savedLocalUserVariables); + } + if (savedLocalLists != null) { + currentSprite.restoreUserDataValues(currentSprite.getUserLists(), savedLocalLists); + } } private void refreshFragmentAfterUndo() { From 62f5304c33c004afca0006c27b39f31c96c1c670 Mon Sep 17 00:00:00 2001 From: harshsomankar123-tech Date: Sat, 28 Mar 2026 00:59:27 +0530 Subject: [PATCH 03/18] IDE-309 Fix Formula Editor Undo regression: Add UI refresh and selective null handling --- .../uiespresso/ui/fragment/UndoTest.java | 13 ++++ .../recyclerview/fragment/ScriptFragment.java | 71 ++++++++++++------- .../scratchconverter/JobHandlerEventTest.java | 2 +- 3 files changed, 61 insertions(+), 25 deletions(-) diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/UndoTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/UndoTest.java index b8f5be45018..2572d4a1aa8 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/UndoTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/UndoTest.java @@ -136,6 +136,19 @@ public void checkScriptAfterUndo() { onBrickAtPosition(brickPosition).checkShowsText(brickText); } + @Test + public void testImmediateUndoRefresh() { + onBrickAtPosition(brickPosition).performDeleteBrick(); + + onView(withId(R.id.menu_undo)) + .perform(click()); + + onView(withId(R.id.menu_undo)) + .check(doesNotExist()); + + onBrickAtPosition(brickPosition).checkShowsText(brickText); + } + public String getProjectAsXmlString() { return XstreamSerializer.getInstance().getXmlAsStringFromProject(ProjectManager.getInstance().getCurrentProject()); } diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java index 36a47d4f65d..256b786250b 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java @@ -914,9 +914,13 @@ public void loadProjectAfterUndoOption() { } } + @Override public void onLoadFinished(boolean success) { ProjectManager.getInstance().setCurrentSceneAndSprite(currentSceneName, currentSpriteName); + if (adapter != null) { + adapter.updateItems(ProjectManager.getInstance().getCurrentSprite()); + } if (checkVariables()) { loadVariables(); } @@ -940,16 +944,27 @@ public boolean checkVariables() { Sprite currentSprite = projectManager.getCurrentSprite(); Project project = projectManager.getCurrentProject(); - return (savedUserVariables != null - && project.hasUserDataChanged(project.getUserVariables(), savedUserVariables)) - || (savedMultiplayerVariables != null - && project.hasUserDataChanged(project.getMultiplayerVariables(), savedMultiplayerVariables)) - || (savedUserLists != null - && project.hasUserDataChanged(project.getUserLists(), savedUserLists)) - || (savedLocalUserVariables != null - && currentSprite.hasUserDataChanged(currentSprite.getUserVariables(), savedLocalUserVariables)) - || (savedLocalLists != null - && currentSprite.hasUserDataChanged(currentSprite.getUserLists(), savedLocalLists)); + boolean changed = false; + if (project != null) { + if (savedUserVariables != null) { + changed |= project.hasUserDataChanged(project.getUserVariables(), savedUserVariables); + } + if (savedMultiplayerVariables != null) { + changed |= project.hasUserDataChanged(project.getMultiplayerVariables(), savedMultiplayerVariables); + } + if (savedUserLists != null) { + changed |= project.hasUserDataChanged(project.getUserLists(), savedUserLists); + } + } + if (currentSprite != null) { + if (savedLocalUserVariables != null) { + changed |= currentSprite.hasUserDataChanged(currentSprite.getUserVariables(), savedLocalUserVariables); + } + if (savedLocalLists != null) { + changed |= currentSprite.hasUserDataChanged(currentSprite.getUserLists(), savedLocalLists); + } + } + return changed; } private void loadVariables() { @@ -957,29 +972,37 @@ private void loadVariables() { Sprite currentSprite = projectManager.getCurrentSprite(); Project project = projectManager.getCurrentProject(); - if (savedUserVariables != null) { - project.restoreUserDataValues(project.getUserVariables(), savedUserVariables); - } - if (savedMultiplayerVariables != null) { - project.restoreUserDataValues(project.getMultiplayerVariables(), savedMultiplayerVariables); - } - if (savedUserLists != null) { - project.restoreUserDataValues(project.getUserLists(), savedUserLists); - } - if (savedLocalUserVariables != null) { - currentSprite.restoreUserDataValues(currentSprite.getUserVariables(), savedLocalUserVariables); + if (project != null) { + if (savedUserVariables != null) { + project.restoreUserDataValues(project.getUserVariables(), savedUserVariables); + } + if (savedMultiplayerVariables != null) { + project.restoreUserDataValues(project.getMultiplayerVariables(), savedMultiplayerVariables); + } + if (savedUserLists != null) { + project.restoreUserDataValues(project.getUserLists(), savedUserLists); + } } - if (savedLocalLists != null) { - currentSprite.restoreUserDataValues(currentSprite.getUserLists(), savedLocalLists); + if (currentSprite != null) { + if (savedLocalUserVariables != null) { + currentSprite.restoreUserDataValues(currentSprite.getUserVariables(), savedLocalUserVariables); + } + if (savedLocalLists != null) { + currentSprite.restoreUserDataValues(currentSprite.getUserLists(), savedLocalLists); + } } } private void refreshFragmentAfterUndo() { Fragment scriptFragment = getActivity().getSupportFragmentManager().findFragmentByTag(TAG); + if (scriptFragment == null) { + return; + } final FragmentTransaction fragmentTransaction = getActivity().getSupportFragmentManager().beginTransaction(); fragmentTransaction.detach(scriptFragment); fragmentTransaction.attach(scriptFragment); - fragmentTransaction.commit(); + fragmentTransaction.commitNow(); + if (undoBrickPosition < listView.getFirstVisiblePosition() || undoBrickPosition > listView.getLastVisiblePosition()) { listView.post(() -> listView.setSelection(undoBrickPosition)); } diff --git a/catroid/src/test/java/org/catrobat/catroid/test/scratchconverter/JobHandlerEventTest.java b/catroid/src/test/java/org/catrobat/catroid/test/scratchconverter/JobHandlerEventTest.java index 4e4e94f6151..2867feba1f0 100644 --- a/catroid/src/test/java/org/catrobat/catroid/test/scratchconverter/JobHandlerEventTest.java +++ b/catroid/src/test/java/org/catrobat/catroid/test/scratchconverter/JobHandlerEventTest.java @@ -107,6 +107,6 @@ public void testReceivedOnJobScheduledEvent() { verify(jobMock, times(1)).getJobID(); verifyNoMoreInteractions(jobMock); - verifyNoInteractions(convertCallbackMock); + verifyNoMoreInteractions(convertCallbackMock); } } From d8d14b7e2d5e1f0ca1e8017dea04defe3983ef1d Mon Sep 17 00:00:00 2001 From: harshsomankar123-tech Date: Sat, 28 Mar 2026 14:57:57 +0530 Subject: [PATCH 04/18] IDE-309: Explicitly hide undo button and add regression tests for toolbar visibility --- .../formulaeditor/FormulaEditorUndoTest.java | 32 +++++++++++++++++++ .../uiespresso/ui/fragment/UndoTest.java | 19 +++++++++++ .../recyclerview/fragment/ScriptFragment.java | 5 +++ 3 files changed, 56 insertions(+) diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.java index 32203486c7f..c61ef98bc2d 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.java @@ -46,6 +46,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; +import static org.catrobat.catroid.WaitForConditionAction.waitFor; import static org.catrobat.catroid.uiespresso.content.brick.utils.BrickDataInteractionWrapper.onBrickAtPosition; import static org.catrobat.catroid.uiespresso.content.brick.utils.ColorPickerInteractionWrapper.onColorPickerPresetButton; import static org.catrobat.catroid.uiespresso.formulaeditor.utils.FormulaEditorDataListWrapper.onDataList; @@ -73,6 +74,7 @@ public class FormulaEditorUndoTest { private static final String NEW_VARIABLE_NAME = "NewVariable"; private static final int VARIABLE_VALUE = 5; private static final String NEW_VARIABLE_VALUE = "10"; + private final long waitThreshold = 5000; UserVariable userVariable; @Rule @@ -485,4 +487,34 @@ public void testUndoAfterActivityRecreationDoesNotCrash() { .onFormulaTextField(R.id.brick_place_at_edit_text_x) .checkShowsNumber(999); } + + @Category({Cat.AppUi.class, Level.Smoke.class}) + @Test + public void testUndoButtonHiddenAfterFormulaEditorUndo() { + onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at); + onView(withId(R.id.brick_place_at_edit_text_x)) + .perform(click()); + onView(withText(R.string.brick_context_dialog_formula_edit_brick)) + .perform(click()); + onFormulaEditor() + .performEnterFormula("1234"); + + pressBack(); + + onView(withId(R.id.menu_undo)) + .perform(waitFor(isDisplayed(), waitThreshold)); + + onView(withId(R.id.menu_undo)) + .perform(click()); + + onView(withId(R.id.menu_undo)) + .check(doesNotExist()); + + onBrickAtPosition(brickPosition) + .onFormulaTextField(R.id.brick_place_at_edit_text_x) + .checkShowsNumber(0); + + onView(withId(R.id.menu_undo)) + .check(doesNotExist()); + } } diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/UndoTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/UndoTest.java index 2572d4a1aa8..5fd885f8979 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/UndoTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/UndoTest.java @@ -149,6 +149,25 @@ public void testImmediateUndoRefresh() { onBrickAtPosition(brickPosition).checkShowsText(brickText); } + @Test + public void testUndoButtonHiddenAfterScriptViewUndo() { + onBrickAtPosition(brickPosition).performDeleteBrick(); + + onView(withId(R.id.menu_undo)) + .perform(waitFor(isDisplayed(), waitThreshold)); + + onView(withId(R.id.menu_undo)) + .perform(click()); + + onView(withId(R.id.menu_undo)) + .check(doesNotExist()); + + onBrickAtPosition(brickPosition).checkShowsText(brickText); + + onView(withId(R.id.menu_undo)) + .check(doesNotExist()); + } + public String getProjectAsXmlString() { return XstreamSerializer.getInstance().getXmlAsStringFromProject(ProjectManager.getInstance().getCurrentProject()); } diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java index 256b786250b..e3e5abd5ffe 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java @@ -906,6 +906,11 @@ public void loadProjectAfterUndoOption() { if (currentCodeFile.exists()) { try { + SpriteActivity spriteActivity = (SpriteActivity) getActivity(); + if (spriteActivity != null) { + spriteActivity.setUndoMenuItemVisibility(false); + spriteActivity.showUndo(false); + } StorageOperations.transferData(undoCodeFile, currentCodeFile); new ProjectLoader(project.getDirectory(), getContext()).setListener(this).loadProjectAsync(); } catch (IOException exception) { From 23624a101746b1678c8dd9e230d6a2811b844d63 Mon Sep 17 00:00:00 2001 From: harshsomankar123-tech Date: Sat, 28 Mar 2026 23:31:46 +0530 Subject: [PATCH 05/18] IDE-309: Fix formula-editor undo path - always restore variables and ensure undo button hidden after reload - Always call loadVariables() unconditionally in onLoadFinished() since UserVariable.value is transient and project reload resets all values to defaults. Previously it was gated on checkVariables() which could miss value-only changes. - Explicitly hide undo button after refreshFragmentAfterUndo() to prevent the detach/attach lifecycle from re-showing it via onPrepareOptionsMenu(). --- .../catroid/ui/recyclerview/fragment/ScriptFragment.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java index e3e5abd5ffe..c1049d63be0 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java @@ -926,10 +926,13 @@ public void onLoadFinished(boolean success) { if (adapter != null) { adapter.updateItems(ProjectManager.getInstance().getCurrentSprite()); } - if (checkVariables()) { - loadVariables(); - } + loadVariables(); refreshFragmentAfterUndo(); + SpriteActivity spriteActivity = (SpriteActivity) getActivity(); + if (spriteActivity != null) { + spriteActivity.setUndoMenuItemVisibility(false); + spriteActivity.showUndo(false); + } } private void saveVariables() { From 9724089dbd595f6513ba98ae0aaa80f0ae341885 Mon Sep 17 00:00:00 2001 From: harshsomankar123-tech Date: Sun, 29 Mar 2026 02:17:23 +0530 Subject: [PATCH 06/18] Fix testUndoAfterActivityRecreationDoesNotCrash Replace activity.recreate() with finish()+startActivity() pattern to avoid NoActivityResumedException. The recreate() call leaves a stale activity reference in ActivityTestRule, causing Espresso to fail when interacting with the new activity instance. This follows the established pattern used in FormulaEditorFragmentActivityRecreateRegressionTest. --- .../formulaeditor/FormulaEditorUndoTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.java index c61ef98bc2d..96261225a1b 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.java @@ -23,6 +23,7 @@ package org.catrobat.catroid.uiespresso.formulaeditor; +import android.content.Intent; import org.catrobat.catroid.ProjectManager; import org.catrobat.catroid.R; import org.catrobat.catroid.content.Script; @@ -42,10 +43,8 @@ import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; - import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; - import static org.catrobat.catroid.WaitForConditionAction.waitFor; import static org.catrobat.catroid.uiespresso.content.brick.utils.BrickDataInteractionWrapper.onBrickAtPosition; import static org.catrobat.catroid.uiespresso.content.brick.utils.ColorPickerInteractionWrapper.onColorPickerPresetButton; @@ -54,7 +53,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; - import static androidx.test.espresso.Espresso.closeSoftKeyboard; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.Espresso.pressBack; @@ -463,9 +461,11 @@ public void testUndoAfterActivityRecreationDoesNotCrash() { onView(withId(R.id.menu_undo)) .check(matches(isDisplayed())); + Intent intent = baseActivityTestRule.getActivity().getIntent(); InstrumentationRegistry.getInstrumentation().runOnMainSync( - () -> baseActivityTestRule.getActivity().recreate()); - + () -> baseActivityTestRule.getActivity().finish()); + InstrumentationRegistry.getInstrumentation().runOnMainSync( + () -> baseActivityTestRule.getActivity().startActivity(intent)); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at); From 3288bf159ddcb7aaa70d0ac7d7b21f8fbd7fefbb Mon Sep 17 00:00:00 2001 From: harshsomankar123-tech Date: Sun, 29 Mar 2026 13:36:59 +0530 Subject: [PATCH 07/18] Fix import separation in FormulaEditorUndoTest to satisfy Checkstyle --- .../catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.java index 96261225a1b..6f2cc0ebb68 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.java @@ -45,6 +45,7 @@ import org.junit.runner.RunWith; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; + import static org.catrobat.catroid.WaitForConditionAction.waitFor; import static org.catrobat.catroid.uiespresso.content.brick.utils.BrickDataInteractionWrapper.onBrickAtPosition; import static org.catrobat.catroid.uiespresso.content.brick.utils.ColorPickerInteractionWrapper.onColorPickerPresetButton; From 3baa9b347ea51e0463147a8df3a0ac58e4a8a704 Mon Sep 17 00:00:00 2001 From: harshsomankar123-tech Date: Wed, 1 Apr 2026 00:29:47 +0530 Subject: [PATCH 08/18] Fix review: add guards in onLoadFinished/refreshFragmentAfterUndo, stabilize UndoTest --- .../uiespresso/ui/fragment/UndoTest.java | 3 +++ .../recyclerview/fragment/ScriptFragment.java | 25 ++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/UndoTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/UndoTest.java index 5fd885f8979..72b2412e88d 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/UndoTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/UndoTest.java @@ -140,6 +140,9 @@ public void checkScriptAfterUndo() { public void testImmediateUndoRefresh() { onBrickAtPosition(brickPosition).performDeleteBrick(); + onView(withId(R.id.menu_undo)) + .perform(waitFor(isDisplayed(), waitThreshold)); + onView(withId(R.id.menu_undo)) .perform(click()); diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java index c1049d63be0..17633f59cbd 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java @@ -922,7 +922,21 @@ public void loadProjectAfterUndoOption() { @Override public void onLoadFinished(boolean success) { - ProjectManager.getInstance().setCurrentSceneAndSprite(currentSceneName, currentSpriteName); + if (!success) { + Log.e(TAG, "Loading project after undo failed."); + if (getContext() != null) { + ToastUtil.showError(getContext(), R.string.error_load_project); + } + SpriteActivity spriteActivity = (SpriteActivity) getActivity(); + if (spriteActivity != null) { + spriteActivity.setUndoMenuItemVisibility(false); + spriteActivity.showUndo(false); + } + return; + } + if (!ProjectManager.getInstance().setCurrentSceneAndSprite(currentSceneName, currentSpriteName)) { + Log.e(TAG, "Could not set scene/sprite after undo: " + currentSceneName + "/" + currentSpriteName); + } if (adapter != null) { adapter.updateItems(ProjectManager.getInstance().getCurrentSprite()); } @@ -1002,14 +1016,17 @@ private void loadVariables() { } private void refreshFragmentAfterUndo() { - Fragment scriptFragment = getActivity().getSupportFragmentManager().findFragmentByTag(TAG); + if (!isAdded() || getActivity() == null || getParentFragmentManager().isStateSaved()) { + return; + } + Fragment scriptFragment = getParentFragmentManager().findFragmentByTag(TAG); if (scriptFragment == null) { return; } - final FragmentTransaction fragmentTransaction = getActivity().getSupportFragmentManager().beginTransaction(); + final FragmentTransaction fragmentTransaction = getParentFragmentManager().beginTransaction(); fragmentTransaction.detach(scriptFragment); fragmentTransaction.attach(scriptFragment); - fragmentTransaction.commitNow(); + fragmentTransaction.commit(); if (undoBrickPosition < listView.getFirstVisiblePosition() || undoBrickPosition > listView.getLastVisiblePosition()) { listView.post(() -> listView.setSelection(undoBrickPosition)); From c373301b2e579c0702f13275659528e978006313 Mon Sep 17 00:00:00 2001 From: harshsomankar123-tech Date: Wed, 1 Apr 2026 13:45:57 +0530 Subject: [PATCH 09/18] IDE-309: Improve undo stability: Handle async failures, cleanup undo file, and fix BrickAdapter sync --- .../recyclerview/fragment/ScriptFragment.java | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java index 17633f59cbd..6693b6a1a2a 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java @@ -904,17 +904,28 @@ public void loadProjectAfterUndoOption() { File currentCodeFile = new File(project.getDirectory(), CODE_XML_FILE_NAME); File undoCodeFile = new File(project.getDirectory(), UNDO_CODE_XML_FILE_NAME); + if (!undoCodeFile.exists()) { + Log.e(TAG, "Undo code file " + UNDO_CODE_XML_FILE_NAME + " does not exist."); + if (getContext() != null) { + ToastUtil.showError(getContext(), R.string.error_load_project); + } + return; + } + if (currentCodeFile.exists()) { try { + StorageOperations.transferData(undoCodeFile, currentCodeFile); SpriteActivity spriteActivity = (SpriteActivity) getActivity(); if (spriteActivity != null) { spriteActivity.setUndoMenuItemVisibility(false); spriteActivity.showUndo(false); } - StorageOperations.transferData(undoCodeFile, currentCodeFile); new ProjectLoader(project.getDirectory(), getContext()).setListener(this).loadProjectAsync(); } catch (IOException exception) { - Log.e(TAG, "Replaceing project " + project.getName() + " failed.", exception); + Log.e(TAG, "Replacing project " + project.getName() + " failed.", exception); + if (getContext() != null) { + ToastUtil.showError(getContext(), R.string.error_load_project); + } } } } @@ -922,31 +933,42 @@ public void loadProjectAfterUndoOption() { @Override public void onLoadFinished(boolean success) { + if (!isAdded() || getContext() == null) { + return; + } + SpriteActivity spriteActivity = (SpriteActivity) getActivity(); if (!success) { Log.e(TAG, "Loading project after undo failed."); - if (getContext() != null) { - ToastUtil.showError(getContext(), R.string.error_load_project); - } - SpriteActivity spriteActivity = (SpriteActivity) getActivity(); + ToastUtil.showError(getContext(), R.string.error_load_project); if (spriteActivity != null) { - spriteActivity.setUndoMenuItemVisibility(false); - spriteActivity.showUndo(false); + spriteActivity.setUndoMenuItemVisibility(true); + spriteActivity.showUndo(true); } return; } if (!ProjectManager.getInstance().setCurrentSceneAndSprite(currentSceneName, currentSpriteName)) { Log.e(TAG, "Could not set scene/sprite after undo: " + currentSceneName + "/" + currentSpriteName); } - if (adapter != null) { - adapter.updateItems(ProjectManager.getInstance().getCurrentSprite()); - } + + adapter = new BrickAdapter(ProjectManager.getInstance().getCurrentSprite()); + adapter.setSelectionListener(this); + adapter.setOnItemClickListener(this); + listView.setAdapter(adapter); + listView.setOnItemClickListener(adapter); + listView.setOnItemLongClickListener(adapter); + loadVariables(); refreshFragmentAfterUndo(); - SpriteActivity spriteActivity = (SpriteActivity) getActivity(); if (spriteActivity != null) { spriteActivity.setUndoMenuItemVisibility(false); spriteActivity.showUndo(false); } + + Project project = ProjectManager.getInstance().getCurrentProject(); + File undoCodeFile = new File(project.getDirectory(), UNDO_CODE_XML_FILE_NAME); + if (undoCodeFile.exists()) { + undoCodeFile.delete(); + } } private void saveVariables() { From 930e4f0998649080d557ef7bb6a2a5b45d4bba2a Mon Sep 17 00:00:00 2001 From: harshsomankar123-tech Date: Wed, 1 Apr 2026 13:53:47 +0530 Subject: [PATCH 10/18] IDE-309: Fix SonarCloud Reliability issues (handled File.delete() return value and added context guards) --- .../catroid/ui/recyclerview/fragment/ScriptFragment.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java index 6693b6a1a2a..eeb27eb1806 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java @@ -920,7 +920,9 @@ public void loadProjectAfterUndoOption() { spriteActivity.setUndoMenuItemVisibility(false); spriteActivity.showUndo(false); } - new ProjectLoader(project.getDirectory(), getContext()).setListener(this).loadProjectAsync(); + if (getContext() != null) { + new ProjectLoader(project.getDirectory(), getContext()).setListener(this).loadProjectAsync(); + } } catch (IOException exception) { Log.e(TAG, "Replacing project " + project.getName() + " failed.", exception); if (getContext() != null) { @@ -966,8 +968,8 @@ public void onLoadFinished(boolean success) { Project project = ProjectManager.getInstance().getCurrentProject(); File undoCodeFile = new File(project.getDirectory(), UNDO_CODE_XML_FILE_NAME); - if (undoCodeFile.exists()) { - undoCodeFile.delete(); + if (undoCodeFile.exists() && !undoCodeFile.delete()) { + Log.w(TAG, "Could not delete undo code file: " + undoCodeFile.getAbsolutePath()); } } From c27e93507815ffc27c54194ccecb5d44cb8453f4 Mon Sep 17 00:00:00 2001 From: harshsomankar123-tech Date: Thu, 2 Apr 2026 17:14:54 +0530 Subject: [PATCH 11/18] IDE-309: Fix FormulaEditorUndoTest NPE in checkVariables and address PR suggestions in ScriptFragment --- .../recyclerview/fragment/ScriptFragment.java | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java index eeb27eb1806..9a6abe33878 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java @@ -935,7 +935,7 @@ public void loadProjectAfterUndoOption() { @Override public void onLoadFinished(boolean success) { - if (!isAdded() || getContext() == null) { + if (!isAdded() || getContext() == null || getView() == null || listView == null) { return; } SpriteActivity spriteActivity = (SpriteActivity) getActivity(); @@ -948,17 +948,11 @@ public void onLoadFinished(boolean success) { } return; } + if (!ProjectManager.getInstance().setCurrentSceneAndSprite(currentSceneName, currentSpriteName)) { Log.e(TAG, "Could not set scene/sprite after undo: " + currentSceneName + "/" + currentSpriteName); } - adapter = new BrickAdapter(ProjectManager.getInstance().getCurrentSprite()); - adapter.setSelectionListener(this); - adapter.setOnItemClickListener(this); - listView.setAdapter(adapter); - listView.setOnItemClickListener(adapter); - listView.setOnItemLongClickListener(adapter); - loadVariables(); refreshFragmentAfterUndo(); if (spriteActivity != null) { @@ -992,21 +986,21 @@ public boolean checkVariables() { boolean changed = false; if (project != null) { - if (savedUserVariables != null) { + if (savedUserVariables != null && project.getUserVariables() != null) { changed |= project.hasUserDataChanged(project.getUserVariables(), savedUserVariables); } - if (savedMultiplayerVariables != null) { + if (savedMultiplayerVariables != null && project.getMultiplayerVariables() != null) { changed |= project.hasUserDataChanged(project.getMultiplayerVariables(), savedMultiplayerVariables); } - if (savedUserLists != null) { + if (savedUserLists != null && project.getUserLists() != null) { changed |= project.hasUserDataChanged(project.getUserLists(), savedUserLists); } } if (currentSprite != null) { - if (savedLocalUserVariables != null) { + if (savedLocalUserVariables != null && currentSprite.getUserVariables() != null) { changed |= currentSprite.hasUserDataChanged(currentSprite.getUserVariables(), savedLocalUserVariables); } - if (savedLocalLists != null) { + if (savedLocalLists != null && currentSprite.getUserLists() != null) { changed |= currentSprite.hasUserDataChanged(currentSprite.getUserLists(), savedLocalLists); } } @@ -1050,7 +1044,7 @@ private void refreshFragmentAfterUndo() { final FragmentTransaction fragmentTransaction = getParentFragmentManager().beginTransaction(); fragmentTransaction.detach(scriptFragment); fragmentTransaction.attach(scriptFragment); - fragmentTransaction.commit(); + fragmentTransaction.commitNow(); if (undoBrickPosition < listView.getFirstVisiblePosition() || undoBrickPosition > listView.getLastVisiblePosition()) { listView.post(() -> listView.setSelection(undoBrickPosition)); From 2e38916e6b213615ed0919082bef2d37031d8e56 Mon Sep 17 00:00:00 2001 From: harshsomankar123-tech Date: Fri, 3 Apr 2026 03:45:57 +0530 Subject: [PATCH 12/18] IDE-309 Guard undo reload when fragment is detached --- .../recyclerview/fragment/ScriptFragment.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java index 9a6abe33878..04c786eeb1d 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java @@ -903,12 +903,16 @@ public void loadProjectAfterUndoOption() { Project project = ProjectManager.getInstance().getCurrentProject(); File currentCodeFile = new File(project.getDirectory(), CODE_XML_FILE_NAME); File undoCodeFile = new File(project.getDirectory(), UNDO_CODE_XML_FILE_NAME); + Context context = getContext(); + + if (!isAdded() || context == null) { + Log.w(TAG, "Cannot load project after undo because fragment is not attached."); + return; + } if (!undoCodeFile.exists()) { Log.e(TAG, "Undo code file " + UNDO_CODE_XML_FILE_NAME + " does not exist."); - if (getContext() != null) { - ToastUtil.showError(getContext(), R.string.error_load_project); - } + ToastUtil.showError(context, R.string.error_load_project); return; } @@ -920,14 +924,10 @@ public void loadProjectAfterUndoOption() { spriteActivity.setUndoMenuItemVisibility(false); spriteActivity.showUndo(false); } - if (getContext() != null) { - new ProjectLoader(project.getDirectory(), getContext()).setListener(this).loadProjectAsync(); - } + new ProjectLoader(project.getDirectory(), context).setListener(this).loadProjectAsync(); } catch (IOException exception) { Log.e(TAG, "Replacing project " + project.getName() + " failed.", exception); - if (getContext() != null) { - ToastUtil.showError(getContext(), R.string.error_load_project); - } + ToastUtil.showError(context, R.string.error_load_project); } } } From 594ff0bf9144da349895ba3e08a6f7e017b2dee0 Mon Sep 17 00:00:00 2001 From: harshsomankar123-tech Date: Tue, 14 Apr 2026 01:25:34 +0530 Subject: [PATCH 13/18] Fix Formula Editor undo persistence and activity recreation regression - Enabled activity recreation support in SpriteActivity by setting savedInstanceStateExpected in onCreate. - Prevented ScriptFragment from wiping the undo visibility flag during configuration changes in onPause. - Implemented state saving and restoration for isUndoMenuItemVisible in SpriteActivity. - Migrated FormulaEditorUndoTest to Kotlin and added safety checks for fragment attachment. --- .../formulaeditor/FormulaEditorUndoTest.java | 521 ----------------- .../formulaeditor/FormulaEditorUndoTest.kt | 522 ++++++++++++++++++ .../org/catrobat/catroid/ui/BaseActivity.kt | 2 +- .../catrobat/catroid/ui/SpriteActivity.java | 18 +- .../recyclerview/fragment/ScriptFragment.java | 28 +- 5 files changed, 559 insertions(+), 532 deletions(-) delete mode 100644 catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.java create mode 100644 catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.kt diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.java deleted file mode 100644 index 6f2cc0ebb68..00000000000 --- a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.java +++ /dev/null @@ -1,521 +0,0 @@ -/* - * Catroid: An on-device visual programming system for Android devices - * Copyright (C) 2010-2025 The Catrobat Team - * () - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * An additional term exception under section 7 of the GNU Affero - * General Public License, version 3, is available at - * http://developer.catrobat.org/license_additional_term - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package org.catrobat.catroid.uiespresso.formulaeditor; - -import android.content.Intent; -import org.catrobat.catroid.ProjectManager; -import org.catrobat.catroid.R; -import org.catrobat.catroid.content.Script; -import org.catrobat.catroid.content.bricks.PlaceAtBrick; -import org.catrobat.catroid.content.bricks.SetVariableBrick; -import org.catrobat.catroid.formulaeditor.Formula; -import org.catrobat.catroid.formulaeditor.UserVariable; -import org.catrobat.catroid.test.utils.TestUtils; -import org.catrobat.catroid.testsuites.annotations.Cat; -import org.catrobat.catroid.testsuites.annotations.Level; -import org.catrobat.catroid.ui.SpriteActivity; -import org.catrobat.catroid.uiespresso.util.UiTestUtils; -import org.catrobat.catroid.uiespresso.util.rules.FragmentActivityTestRule; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.junit.runner.RunWith; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.platform.app.InstrumentationRegistry; - -import static org.catrobat.catroid.WaitForConditionAction.waitFor; -import static org.catrobat.catroid.uiespresso.content.brick.utils.BrickDataInteractionWrapper.onBrickAtPosition; -import static org.catrobat.catroid.uiespresso.content.brick.utils.ColorPickerInteractionWrapper.onColorPickerPresetButton; -import static org.catrobat.catroid.uiespresso.formulaeditor.utils.FormulaEditorDataListWrapper.onDataList; -import static org.catrobat.catroid.uiespresso.formulaeditor.utils.FormulaEditorWrapper.onFormulaEditor; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static androidx.test.espresso.Espresso.closeSoftKeyboard; -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.Espresso.pressBack; -import static androidx.test.espresso.action.ViewActions.click; -import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static androidx.test.espresso.matcher.ViewMatchers.withText; - -@RunWith(AndroidJUnit4.class) -public class FormulaEditorUndoTest { - - private int brickPosition; - - private static final String VARIABLE_NAME = "TestVariable"; - private static final String NEW_VARIABLE_NAME = "NewVariable"; - private static final int VARIABLE_VALUE = 5; - private static final String NEW_VARIABLE_VALUE = "10"; - private final long waitThreshold = 5000; - UserVariable userVariable; - - @Rule - public FragmentActivityTestRule baseActivityTestRule = new - FragmentActivityTestRule<>(SpriteActivity.class, SpriteActivity.EXTRA_FRAGMENT_POSITION, SpriteActivity.FRAGMENT_SCRIPTS); - - @After - public void tearDown() throws Exception { - TestUtils.deleteProjects(FormulaEditorUndoTest.class.getName()); - } - - @Before - public void setUp() throws Exception { - brickPosition = 1; - Script script = UiTestUtils.createProjectAndGetStartScript(FormulaEditorUndoTest.class.getName()); - script.addBrick(new PlaceAtBrick()); - userVariable = new UserVariable(VARIABLE_NAME, VARIABLE_VALUE); - ProjectManager.getInstance().getCurrentProject().addUserVariable(userVariable); - script.addBrick(new SetVariableBrick(new Formula(0), userVariable)); - baseActivityTestRule.launchActivity(); - } - - @Category({Cat.AppUi.class, Level.Smoke.class}) - @Test - public void testUndoFormulaChangesOnPressBack() { - onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at); - onView(withId(R.id.brick_place_at_edit_text_x)) - .perform(click()); - onView(withText(R.string.brick_context_dialog_formula_edit_brick)) - .perform(click()); - onFormulaEditor() - .performEnterFormula("1234"); - - onView(withId(R.id.brick_place_at_edit_text_y)) - .perform(click()); - onView(withText(R.string.brick_context_dialog_formula_edit_brick)) - .perform(click()); - onFormulaEditor() - .performEnterFormula("745"); - - pressBack(); - - onView(withId(R.id.menu_undo)) - .check(matches(isDisplayed())); - onBrickAtPosition(brickPosition) - .onFormulaTextField(R.id.brick_place_at_edit_text_x) - .checkShowsNumber(1234); - onBrickAtPosition(brickPosition) - .onFormulaTextField(R.id.brick_place_at_edit_text_y) - .checkShowsNumber(745); - - onView(withId(R.id.menu_undo)) - .perform(click()); - onView(withId(R.id.menu_undo)) - .check(doesNotExist()); - - onBrickAtPosition(brickPosition) - .onFormulaTextField(R.id.brick_place_at_edit_text_x) - .checkShowsNumber(0); - onBrickAtPosition(brickPosition) - .onFormulaTextField(R.id.brick_place_at_edit_text_y) - .checkShowsNumber(0); - } - - @Category({Cat.AppUi.class, Level.Smoke.class}) - @Test - public void testUndoFormulaWithNoChanges() { - onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at); - - onView(withId(R.id.brick_place_at_edit_text_x)) - .perform(click()); - onView(withText(R.string.brick_context_dialog_formula_edit_brick)) - .perform(click()); - - pressBack(); - - onView(withId(R.id.menu_undo)) - .check(doesNotExist()); - - onBrickAtPosition(brickPosition) - .onFormulaTextField(R.id.brick_place_at_edit_text_x) - .checkShowsNumber(0); - onBrickAtPosition(brickPosition) - .onFormulaTextField(R.id.brick_place_at_edit_text_y) - .checkShowsNumber(0); - } - - @Category({Cat.AppUi.class, Level.Smoke.class}) - @Test - public void testUndoFormulaWithRevertedChanges() { - onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at); - onView(withId(R.id.brick_place_at_edit_text_x)) - .perform(click()); - onView(withText(R.string.brick_context_dialog_formula_edit_brick)) - .perform(click()); - onFormulaEditor() - .performEnterFormula("1234"); - - onView(withId(R.id.brick_place_at_edit_text_y)) - .perform(click()); - onView(withText(R.string.brick_context_dialog_formula_edit_brick)) - .perform(click()); - - onView(withId(R.id.brick_place_at_edit_text_x)) - .perform(click()); - onView(withText(R.string.brick_context_dialog_formula_edit_brick)).perform(click()); - onFormulaEditor().performEnterFormula("0"); - - pressBack(); - - onView(withId(R.id.menu_undo)) - .check(doesNotExist()); - - onBrickAtPosition(brickPosition) - .onFormulaTextField(R.id.brick_place_at_edit_text_x) - .checkShowsNumber(0); - onBrickAtPosition(brickPosition) - .onFormulaTextField(R.id.brick_place_at_edit_text_y) - .checkShowsNumber(0); - } - - @Category({Cat.AppUi.class, Level.Smoke.class}) - @Test - public void testUndoFormulaAddVariable() { - onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at); - - onView(withId(R.id.brick_place_at_edit_text_x)) - .perform(click()); - onView(withText(R.string.brick_context_dialog_formula_edit_brick)) - .perform(click()); - - onFormulaEditor() - .performOpenDataFragment(); - - onDataList() - .performAdd(NEW_VARIABLE_NAME); - - onDataList() - .performClose(); - - pressBack(); - - onView(withId(R.id.menu_undo)) - .check(matches(isDisplayed())); - - onView(withId(R.id.menu_undo)) - .perform(click()); - onView(withId(R.id.menu_undo)) - .check(doesNotExist()); - - assertNull(ProjectManager.getInstance().getCurrentProject().getUserVariable(NEW_VARIABLE_NAME)); - - onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at); - - onView(withId(R.id.brick_place_at_edit_text_x)) - .perform(click()); - onView(withText(R.string.brick_context_dialog_formula_edit_brick)) - .perform(click()); - - onFormulaEditor() - .performOpenDataFragment(); - - onDataList() - .performAdd(NEW_VARIABLE_NAME); - - onDataList() - .performClose(); - - pressBack(); - - onView(withId(R.id.menu_undo)) - .check(matches(isDisplayed())); - - assertNotNull(ProjectManager.getInstance().getCurrentProject().getUserVariable(NEW_VARIABLE_NAME)); - } - - @Category({Cat.AppUi.class, Level.Smoke.class}) - @Test - public void testUndoFormulaDeleteVariable() { - onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at); - - onView(withId(R.id.brick_place_at_edit_text_x)) - .perform(click()); - onView(withText(R.string.brick_context_dialog_formula_edit_brick)) - .perform(click()); - - onFormulaEditor() - .performOpenDataFragment(); - - onDataList().onVariableAtPosition(0) - .performDelete(); - - onDataList() - .performClose(); - - pressBack(); - - onView(withId(R.id.menu_undo)) - .check(matches(isDisplayed())); - - onView(withId(R.id.menu_undo)) - .perform(click()); - onView(withId(R.id.menu_undo)) - .check(doesNotExist()); - - assertNotNull(ProjectManager.getInstance().getCurrentProject().getUserVariable(VARIABLE_NAME)); - - assertEquals(ProjectManager.getInstance().getCurrentProject().getUserVariable(VARIABLE_NAME), userVariable); - assertEquals(ProjectManager.getInstance().getCurrentProject().getUserVariable(VARIABLE_NAME).getValue(), VARIABLE_VALUE); - } - - @Category({Cat.AppUi.class, Level.Smoke.class}) - @Test - public void testUndoFormulaRenameVariable() { - onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at); - onView(withId(R.id.brick_place_at_edit_text_x)).perform(click()); - onView(withText(R.string.brick_context_dialog_formula_edit_brick)).perform(click()); - onFormulaEditor().performOpenDataFragment(); - onDataList().onVariableAtPosition(0).performRename(NEW_VARIABLE_NAME); - onDataList().performClose(); - pressBack(); - - onView(withId(R.id.menu_undo)).check(matches(isDisplayed())); - - assertNull(ProjectManager.getInstance().getCurrentProject().getUserVariable(VARIABLE_NAME)); - assertNotNull(ProjectManager.getInstance().getCurrentProject().getUserVariable(NEW_VARIABLE_NAME)); - assertEquals(ProjectManager.getInstance().getCurrentProject().getUserVariable(NEW_VARIABLE_NAME), userVariable); - assertEquals(ProjectManager.getInstance().getCurrentProject().getUserVariable(NEW_VARIABLE_NAME).getValue(), VARIABLE_VALUE); - - onView(withId(R.id.menu_undo)) - .perform(click()); - userVariable.setName(VARIABLE_NAME); - onView(withId(R.id.menu_undo)) - .check(doesNotExist()); - - assertNull(ProjectManager.getInstance().getCurrentProject().getUserVariable(NEW_VARIABLE_NAME)); - assertNotNull(ProjectManager.getInstance().getCurrentProject().getUserVariable(VARIABLE_NAME)); - assertEquals(ProjectManager.getInstance().getCurrentProject().getUserVariable(VARIABLE_NAME), userVariable); - assertEquals(ProjectManager.getInstance().getCurrentProject().getUserVariable(VARIABLE_NAME).getValue(), VARIABLE_VALUE); - } - - @Category({Cat.AppUi.class, Level.Smoke.class}) - @Test - public void testUndoFormulaEditVariable() { - onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at); - - onView(withId(R.id.brick_place_at_edit_text_x)) - .perform(click()); - onView(withText(R.string.brick_context_dialog_formula_edit_brick)) - .perform(click()); - - onFormulaEditor() - .performOpenDataFragment(); - - onDataList().onVariableAtPosition(0) - .performEdit(NEW_VARIABLE_VALUE); - - onDataList() - .performClose(); - - pressBack(); - - onView(withId(R.id.menu_undo)) - .check(matches(isDisplayed())); - - assertEquals(ProjectManager.getInstance().getCurrentProject().getUserVariable(VARIABLE_NAME), userVariable); - assertEquals(ProjectManager.getInstance().getCurrentProject().getUserVariable(VARIABLE_NAME).getValue(), NEW_VARIABLE_VALUE); - - onView(withId(R.id.menu_undo)) - .perform(click()); - onView(withId(R.id.menu_undo)) - .check(doesNotExist()); - - assertEquals(ProjectManager.getInstance().getCurrentProject().getUserVariable(VARIABLE_NAME), userVariable); - assertEquals(ProjectManager.getInstance().getCurrentProject().getUserVariable(VARIABLE_NAME).getValue(), VARIABLE_VALUE); - } - - @Category({Cat.AppUi.class, Level.Smoke.class}) - @Test - public void testUndoFormulaWithSpinnerVariable() { - onBrickAtPosition(2).checkShowsText(R.string.brick_set_variable); - onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at); - - onBrickAtPosition(2) - .onSpinner(R.id.set_variable_spinner) - .checkShowsText(VARIABLE_NAME); - - onView(withId(R.id.brick_place_at_edit_text_x)) - .perform(click()); - onView(withText(R.string.brick_context_dialog_formula_edit_brick)) - .perform(click()); - - onFormulaEditor() - .performOpenDataFragment(); - - onDataList().onVariableAtPosition(0) - .performDelete(); - - onDataList() - .performClose(); - - pressBack(); - - onBrickAtPosition(2) - .onSpinner(R.id.set_variable_spinner) - .checkShowsText(R.string.new_option); - - onView(withId(R.id.menu_undo)) - .check(matches(isDisplayed())); - - onView(withId(R.id.menu_undo)) - .perform(click()); - onView(withId(R.id.menu_undo)) - .check(doesNotExist()); - - onBrickAtPosition(2) - .onSpinner(R.id.set_variable_spinner) - .checkShowsText(VARIABLE_NAME); - } - - @Category({Cat.AppUi.class, Level.Smoke.class}) - @Test - public void testUndoFormulaWithColorPicker() { - onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at); - - onView(withId(R.id.brick_place_at_edit_text_x)) - .perform(click()); - onView(withText(R.string.brick_context_dialog_formula_edit_brick)) - .perform(click()); - - onView(withId(R.id.formula_editor_keyboard_color_picker)).perform(click()); - - onColorPickerPresetButton(0, 0) - .perform(click()); - - closeSoftKeyboard(); - - onView(withText(R.string.color_picker_apply)) - .perform(click()); - - pressBack(); - - onView(withId(R.id.brick_place_at_edit_text_x)) - .check(matches(withText("'#0074CD ' "))); - onBrickAtPosition(brickPosition) - .onFormulaTextField(R.id.brick_place_at_edit_text_y) - .checkShowsNumber(0); - - onView(withId(R.id.menu_undo)) - .check(matches(isDisplayed())); - - onView(withId(R.id.menu_undo)) - .perform(click()); - onView(withId(R.id.menu_undo)) - .check(doesNotExist()); - - onBrickAtPosition(brickPosition) - .onFormulaTextField(R.id.brick_place_at_edit_text_x) - .checkShowsNumber(0); - onBrickAtPosition(brickPosition) - .onFormulaTextField(R.id.brick_place_at_edit_text_y) - .checkShowsNumber(0); - } - - @Category({Cat.AppUi.class, Level.Smoke.class}) - @Test - public void testUndoAfterActivityRecreationDoesNotCrash() { - onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at); - - onView(withId(R.id.brick_place_at_edit_text_x)) - .perform(click()); - onView(withText(R.string.brick_context_dialog_formula_edit_brick)) - .perform(click()); - - onFormulaEditor() - .performOpenDataFragment(); - - onDataList() - .performAdd(NEW_VARIABLE_NAME); - - onDataList() - .performClose(); - - pressBack(); - - onView(withId(R.id.menu_undo)) - .check(matches(isDisplayed())); - - Intent intent = baseActivityTestRule.getActivity().getIntent(); - InstrumentationRegistry.getInstrumentation().runOnMainSync( - () -> baseActivityTestRule.getActivity().finish()); - InstrumentationRegistry.getInstrumentation().runOnMainSync( - () -> baseActivityTestRule.getActivity().startActivity(intent)); - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); - - onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at); - - onView(withId(R.id.brick_place_at_edit_text_x)) - .perform(click()); - onView(withText(R.string.brick_context_dialog_formula_edit_brick)) - .perform(click()); - - onFormulaEditor() - .performEnterFormula("999"); - - pressBack(); - - onView(withId(R.id.menu_undo)) - .check(matches(isDisplayed())); - - onBrickAtPosition(brickPosition) - .onFormulaTextField(R.id.brick_place_at_edit_text_x) - .checkShowsNumber(999); - } - - @Category({Cat.AppUi.class, Level.Smoke.class}) - @Test - public void testUndoButtonHiddenAfterFormulaEditorUndo() { - onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at); - onView(withId(R.id.brick_place_at_edit_text_x)) - .perform(click()); - onView(withText(R.string.brick_context_dialog_formula_edit_brick)) - .perform(click()); - onFormulaEditor() - .performEnterFormula("1234"); - - pressBack(); - - onView(withId(R.id.menu_undo)) - .perform(waitFor(isDisplayed(), waitThreshold)); - - onView(withId(R.id.menu_undo)) - .perform(click()); - - onView(withId(R.id.menu_undo)) - .check(doesNotExist()); - - onBrickAtPosition(brickPosition) - .onFormulaTextField(R.id.brick_place_at_edit_text_x) - .checkShowsNumber(0); - - onView(withId(R.id.menu_undo)) - .check(doesNotExist()); - } -} diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.kt b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.kt new file mode 100644 index 00000000000..974f1e9a649 --- /dev/null +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.kt @@ -0,0 +1,522 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2026 The Catrobat Team + * () + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * An additional term exception under section 7 of the GNU Affero + * General Public License, version 3, is available at + * http://developer.catrobat.org/license_additional_term + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.catrobat.catroid.uiespresso.formulaeditor + +import androidx.test.espresso.Espresso.closeSoftKeyboard +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.Espresso.pressBack +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions.doesNotExist +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.catrobat.catroid.ProjectManager +import org.catrobat.catroid.R +import org.catrobat.catroid.WaitForConditionAction.Companion.waitFor +import org.catrobat.catroid.content.bricks.PlaceAtBrick +import org.catrobat.catroid.content.bricks.SetVariableBrick +import org.catrobat.catroid.formulaeditor.Formula +import org.catrobat.catroid.formulaeditor.UserVariable +import org.catrobat.catroid.test.utils.TestUtils +import org.catrobat.catroid.testsuites.annotations.Cat +import org.catrobat.catroid.testsuites.annotations.Level +import org.catrobat.catroid.ui.SpriteActivity +import org.catrobat.catroid.uiespresso.content.brick.utils.BrickDataInteractionWrapper.onBrickAtPosition +import org.catrobat.catroid.uiespresso.content.brick.utils.ColorPickerInteractionWrapper.onColorPickerPresetButton +import org.catrobat.catroid.uiespresso.formulaeditor.utils.FormulaEditorDataListWrapper.onDataList +import org.catrobat.catroid.uiespresso.formulaeditor.utils.FormulaEditorWrapper.onFormulaEditor +import org.catrobat.catroid.uiespresso.util.UiTestUtils +import org.catrobat.catroid.uiespresso.util.rules.FragmentActivityTestRule +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.experimental.categories.Category +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class FormulaEditorUndoTest { + + private var brickPosition: Int = 0 + + private var userVariable: UserVariable? = null + + @get:Rule + var baseActivityTestRule = FragmentActivityTestRule( + SpriteActivity::class.java, SpriteActivity.EXTRA_FRAGMENT_POSITION, SpriteActivity.FRAGMENT_SCRIPTS + ) + + @After + @Throws(Exception::class) + fun tearDown() { + TestUtils.deleteProjects(FormulaEditorUndoTest::class.java.name) + } + + @Before + @Throws(Exception::class) + fun setUp() { + brickPosition = 1 + val script = UiTestUtils.createProjectAndGetStartScript(FormulaEditorUndoTest::class.java.name) + script.addBrick(PlaceAtBrick()) + userVariable = UserVariable(VARIABLE_NAME, VARIABLE_VALUE) + ProjectManager.getInstance().currentProject.addUserVariable(userVariable) + script.addBrick(SetVariableBrick(Formula(0), userVariable)) + baseActivityTestRule.launchActivity() + } + + @Category(Cat.AppUi::class, Level.Smoke::class) + @Test + fun testUndoFormulaChangesOnPressBack() { + onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at) + onView(withId(R.id.brick_place_at_edit_text_x)) + .perform(click()) + onView(withText(R.string.brick_context_dialog_formula_edit_brick)) + .perform(click()) + onFormulaEditor() + .performEnterFormula("1234") + + onView(withId(R.id.brick_place_at_edit_text_y)) + .perform(click()) + onView(withText(R.string.brick_context_dialog_formula_edit_brick)) + .perform(click()) + onFormulaEditor() + .performEnterFormula("745") + + pressBack() + + onView(withId(R.id.menu_undo)) + .check(matches(isDisplayed())) + onBrickAtPosition(brickPosition) + .onFormulaTextField(R.id.brick_place_at_edit_text_x) + .checkShowsNumber(1234) + onBrickAtPosition(brickPosition) + .onFormulaTextField(R.id.brick_place_at_edit_text_y) + .checkShowsNumber(745) + + onView(withId(R.id.menu_undo)) + .perform(click()) + onView(withId(R.id.menu_undo)) + .check(doesNotExist()) + + onBrickAtPosition(brickPosition) + .onFormulaTextField(R.id.brick_place_at_edit_text_x) + .checkShowsNumber(0) + onBrickAtPosition(brickPosition) + .onFormulaTextField(R.id.brick_place_at_edit_text_y) + .checkShowsNumber(0) + } + + @Category(Cat.AppUi::class, Level.Smoke::class) + @Test + fun testUndoFormulaWithNoChanges() { + onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at) + + onView(withId(R.id.brick_place_at_edit_text_x)) + .perform(click()) + onView(withText(R.string.brick_context_dialog_formula_edit_brick)) + .perform(click()) + + pressBack() + + onView(withId(R.id.menu_undo)) + .check(doesNotExist()) + + onBrickAtPosition(brickPosition) + .onFormulaTextField(R.id.brick_place_at_edit_text_x) + .checkShowsNumber(0) + onBrickAtPosition(brickPosition) + .onFormulaTextField(R.id.brick_place_at_edit_text_y) + .checkShowsNumber(0) + } + + @Category(Cat.AppUi::class, Level.Smoke::class) + @Test + fun testUndoFormulaWithRevertedChanges() { + onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at) + onView(withId(R.id.brick_place_at_edit_text_x)) + .perform(click()) + onView(withText(R.string.brick_context_dialog_formula_edit_brick)) + .perform(click()) + onFormulaEditor() + .performEnterFormula("1234") + + onView(withId(R.id.brick_place_at_edit_text_y)) + .perform(click()) + onView(withText(R.string.brick_context_dialog_formula_edit_brick)) + .perform(click()) + + onView(withId(R.id.brick_place_at_edit_text_x)) + .perform(click()) + onView(withText(R.string.brick_context_dialog_formula_edit_brick)).perform(click()) + onFormulaEditor().performEnterFormula("0") + + pressBack() + + onView(withId(R.id.menu_undo)) + .check(doesNotExist()) + + onBrickAtPosition(brickPosition) + .onFormulaTextField(R.id.brick_place_at_edit_text_x) + .checkShowsNumber(0) + onBrickAtPosition(brickPosition) + .onFormulaTextField(R.id.brick_place_at_edit_text_y) + .checkShowsNumber(0) + } + + @Category(Cat.AppUi::class, Level.Smoke::class) + @Test + fun testUndoFormulaAddVariable() { + onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at) + + onView(withId(R.id.brick_place_at_edit_text_x)) + .perform(click()) + onView(withText(R.string.brick_context_dialog_formula_edit_brick)) + .perform(click()) + + onFormulaEditor() + .performOpenDataFragment() + + onDataList() + .performAdd(NEW_VARIABLE_NAME) + + onDataList() + .performClose() + + pressBack() + + onView(withId(R.id.menu_undo)) + .check(matches(isDisplayed())) + + onView(withId(R.id.menu_undo)) + .perform(click()) + onView(withId(R.id.menu_undo)) + .check(doesNotExist()) + + assertNull(ProjectManager.getInstance().currentProject.getUserVariable(NEW_VARIABLE_NAME)) + + onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at) + + onView(withId(R.id.brick_place_at_edit_text_x)) + .perform(click()) + onView(withText(R.string.brick_context_dialog_formula_edit_brick)) + .perform(click()) + + onFormulaEditor() + .performOpenDataFragment() + + onDataList() + .performAdd(NEW_VARIABLE_NAME) + + onDataList() + .performClose() + + pressBack() + + onView(withId(R.id.menu_undo)) + .check(matches(isDisplayed())) + + assertNotNull(ProjectManager.getInstance().currentProject.getUserVariable(NEW_VARIABLE_NAME)) + } + + @Category(Cat.AppUi::class, Level.Smoke::class) + @Test + fun testUndoFormulaDeleteVariable() { + onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at) + + onView(withId(R.id.brick_place_at_edit_text_x)) + .perform(click()) + onView(withText(R.string.brick_context_dialog_formula_edit_brick)) + .perform(click()) + + onFormulaEditor() + .performOpenDataFragment() + + onDataList().onVariableAtPosition(0) + .performDelete() + + onDataList() + .performClose() + + pressBack() + + onView(withId(R.id.menu_undo)) + .check(matches(isDisplayed())) + + onView(withId(R.id.menu_undo)) + .perform(click()) + onView(withId(R.id.menu_undo)) + .check(doesNotExist()) + + assertNotNull(ProjectManager.getInstance().currentProject.getUserVariable(VARIABLE_NAME)) + + assertEquals(ProjectManager.getInstance().currentProject.getUserVariable(VARIABLE_NAME), userVariable) + assertEquals(ProjectManager.getInstance().currentProject.getUserVariable(VARIABLE_NAME).value, VARIABLE_VALUE) + } + + @Category(Cat.AppUi::class, Level.Smoke::class) + @Test + fun testUndoFormulaRenameVariable() { + onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at) + onView(withId(R.id.brick_place_at_edit_text_x)).perform(click()) + onView(withText(R.string.brick_context_dialog_formula_edit_brick)).perform(click()) + onFormulaEditor().performOpenDataFragment() + onDataList().onVariableAtPosition(0).performRename(NEW_VARIABLE_NAME) + onDataList().performClose() + pressBack() + + onView(withId(R.id.menu_undo)).check(matches(isDisplayed())) + + assertNull(ProjectManager.getInstance().currentProject.getUserVariable(VARIABLE_NAME)) + assertNotNull(ProjectManager.getInstance().currentProject.getUserVariable(NEW_VARIABLE_NAME)) + assertEquals(ProjectManager.getInstance().currentProject.getUserVariable(NEW_VARIABLE_NAME), userVariable) + assertEquals(ProjectManager.getInstance().currentProject.getUserVariable(NEW_VARIABLE_NAME).value, VARIABLE_VALUE) + + onView(withId(R.id.menu_undo)) + .perform(click()) + userVariable!!.name = VARIABLE_NAME + onView(withId(R.id.menu_undo)) + .check(doesNotExist()) + + assertNull(ProjectManager.getInstance().currentProject.getUserVariable(NEW_VARIABLE_NAME)) + assertNotNull(ProjectManager.getInstance().currentProject.getUserVariable(VARIABLE_NAME)) + assertEquals(ProjectManager.getInstance().currentProject.getUserVariable(VARIABLE_NAME), userVariable) + assertEquals(ProjectManager.getInstance().currentProject.getUserVariable(VARIABLE_NAME).value, VARIABLE_VALUE) + } + + @Category(Cat.AppUi::class, Level.Smoke::class) + @Test + fun testUndoFormulaEditVariable() { + onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at) + + onView(withId(R.id.brick_place_at_edit_text_x)) + .perform(click()) + onView(withText(R.string.brick_context_dialog_formula_edit_brick)) + .perform(click()) + + onFormulaEditor() + .performOpenDataFragment() + + onDataList().onVariableAtPosition(0) + .performEdit(NEW_VARIABLE_VALUE) + + onDataList() + .performClose() + + pressBack() + + onView(withId(R.id.menu_undo)) + .check(matches(isDisplayed())) + + assertEquals(ProjectManager.getInstance().currentProject.getUserVariable(VARIABLE_NAME), userVariable) + assertEquals(ProjectManager.getInstance().currentProject.getUserVariable(VARIABLE_NAME).value, NEW_VARIABLE_VALUE) + + onView(withId(R.id.menu_undo)) + .perform(click()) + onView(withId(R.id.menu_undo)) + .check(doesNotExist()) + + assertEquals(ProjectManager.getInstance().currentProject.getUserVariable(VARIABLE_NAME), userVariable) + assertEquals(ProjectManager.getInstance().currentProject.getUserVariable(VARIABLE_NAME).value, VARIABLE_VALUE) + } + + @Category(Cat.AppUi::class, Level.Smoke::class) + @Test + fun testUndoFormulaWithSpinnerVariable() { + onBrickAtPosition(2).checkShowsText(R.string.brick_set_variable) + onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at) + + onBrickAtPosition(2) + .onSpinner(R.id.set_variable_spinner) + .checkShowsText(VARIABLE_NAME) + + onView(withId(R.id.brick_place_at_edit_text_x)) + .perform(click()) + onView(withText(R.string.brick_context_dialog_formula_edit_brick)) + .perform(click()) + + onFormulaEditor() + .performOpenDataFragment() + + onDataList().onVariableAtPosition(0) + .performDelete() + + onDataList() + .performClose() + + pressBack() + + onBrickAtPosition(2) + .onSpinner(R.id.set_variable_spinner) + .checkShowsText(R.string.new_option) + + onView(withId(R.id.menu_undo)) + .check(matches(isDisplayed())) + + onView(withId(R.id.menu_undo)) + .perform(click()) + onView(withId(R.id.menu_undo)) + .check(doesNotExist()) + + onBrickAtPosition(2) + .onSpinner(R.id.set_variable_spinner) + .checkShowsText(VARIABLE_NAME) + } + + @Category(Cat.AppUi::class, Level.Smoke::class) + @Test + fun testUndoFormulaWithColorPicker() { + onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at) + + onView(withId(R.id.brick_place_at_edit_text_x)) + .perform(click()) + onView(withText(R.string.brick_context_dialog_formula_edit_brick)) + .perform(click()) + + onView(withId(R.id.formula_editor_keyboard_color_picker)).perform(click()) + + onColorPickerPresetButton(0, 0) + .perform(click()) + + closeSoftKeyboard() + + onView(withText(R.string.color_picker_apply)) + .perform(click()) + + pressBack() + + onView(withId(R.id.brick_place_at_edit_text_x)) + .check(matches(withText("'#0074CD ' "))) + onBrickAtPosition(brickPosition) + .onFormulaTextField(R.id.brick_place_at_edit_text_y) + .checkShowsNumber(0) + + onView(withId(R.id.menu_undo)) + .check(matches(isDisplayed())) + + onView(withId(R.id.menu_undo)) + .perform(click()) + onView(withId(R.id.menu_undo)) + .check(doesNotExist()) + + onBrickAtPosition(brickPosition) + .onFormulaTextField(R.id.brick_place_at_edit_text_x) + .checkShowsNumber(0) + onBrickAtPosition(brickPosition) + .onFormulaTextField(R.id.brick_place_at_edit_text_y) + .checkShowsNumber(0) + } + + @Category(Cat.AppUi::class, Level.Smoke::class) + @Test + fun testUndoAfterActivityRecreationDoesNotCrash() { + onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at) + + onView(withId(R.id.brick_place_at_edit_text_x)) + .perform(click()) + onView(withText(R.string.brick_context_dialog_formula_edit_brick)) + .perform(click()) + + onFormulaEditor() + .performOpenDataFragment() + + onDataList() + .performAdd(NEW_VARIABLE_NAME) + + onDataList() + .performClose() + + pressBack() + + onView(withId(R.id.menu_undo)) + .check(matches(isDisplayed())) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + baseActivityTestRule.activity.recreate() + } + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + + onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at) + + onView(withId(R.id.brick_place_at_edit_text_x)) + .perform(click()) + onView(withText(R.string.brick_context_dialog_formula_edit_brick)) + .perform(click()) + + onFormulaEditor() + .performEnterFormula("999") + + pressBack() + + onView(withId(R.id.menu_undo)) + .check(matches(isDisplayed())) + + onBrickAtPosition(brickPosition) + .onFormulaTextField(R.id.brick_place_at_edit_text_x) + .checkShowsNumber(999) + } + + @Category(Cat.AppUi::class, Level.Smoke::class) + @Test + fun testUndoButtonHiddenAfterFormulaEditorUndo() { + onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at) + onView(withId(R.id.brick_place_at_edit_text_x)) + .perform(click()) + onView(withText(R.string.brick_context_dialog_formula_edit_brick)) + .perform(click()) + onFormulaEditor() + .performEnterFormula("1234") + + pressBack() + + onView(withId(R.id.menu_undo)) + .perform(waitFor(isDisplayed(), waitThreshold)) + + onView(withId(R.id.menu_undo)) + .perform(click()) + + onView(withId(R.id.menu_undo)) + .check(doesNotExist()) + + onBrickAtPosition(brickPosition) + .onFormulaTextField(R.id.brick_place_at_edit_text_x) + .checkShowsNumber(0) + + onView(withId(R.id.menu_undo)) + .check(doesNotExist()) + } + + companion object { + private const val VARIABLE_NAME = "TestVariable" + private const val NEW_VARIABLE_NAME = "NewVariable" + private const val VARIABLE_VALUE = 5 + private const val NEW_VARIABLE_VALUE = "10" + private const val waitThreshold = 5000L + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/BaseActivity.kt b/catroid/src/main/java/org/catrobat/catroid/ui/BaseActivity.kt index 3f781db0e27..2d6143b2d99 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/BaseActivity.kt +++ b/catroid/src/main/java/org/catrobat/catroid/ui/BaseActivity.kt @@ -55,7 +55,7 @@ internal const val RECOVERED_FROM_CRASH = "RECOVERED_FROM_CRASH" abstract class BaseActivity : AppCompatActivity(), PermissionHandlingActivity { lateinit var optionsMenu: Menu private val permissionRequestActivityExtension = PermissionRequestActivityExtension() - private var savedInstanceStateExpected = false + protected var savedInstanceStateExpected = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/SpriteActivity.java b/catroid/src/main/java/org/catrobat/catroid/ui/SpriteActivity.java index 865c2ab9e37..538b0885055 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/SpriteActivity.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/SpriteActivity.java @@ -139,6 +139,7 @@ public class SpriteActivity extends BaseActivity { public static final String EXTRA_FRAGMENT_POSITION = "fragmentPosition"; public static final String EXTRA_BRICK_HASH = "BRICK_HASH"; + private static final String BUNDLE_IS_UNDO_MENU_ITEM_VISIBLE = "isUndoMenuItemVisible"; public static final String EXTRA_X_TRANSFORM = "X"; public static final String EXTRA_Y_TRANSFORM = "Y"; @@ -162,6 +163,9 @@ public class SpriteActivity extends BaseActivity { @Override public void onCreate(Bundle savedInstanceState) { + if (savedInstanceState != null) { + setSavedInstanceStateExpected(true); + } super.onCreate(savedInstanceState); if (isFinishing()) { @@ -185,10 +189,12 @@ public void onCreate(Bundle savedInstanceState) { int fragmentPosition = FRAGMENT_SCRIPTS; Bundle bundle = getIntent().getExtras(); - if (bundle != null) { - fragmentPosition = bundle.getInt(EXTRA_FRAGMENT_POSITION, FRAGMENT_SCRIPTS); + if (savedInstanceState != null) { + isUndoMenuItemVisible = savedInstanceState.getBoolean(BUNDLE_IS_UNDO_MENU_ITEM_VISIBLE, false); + invalidateOptionsMenu(); + } else { + loadFragment(this, fragmentPosition); } - loadFragment(this, fragmentPosition); addTabLayout(this, fragmentPosition); } @@ -276,6 +282,12 @@ protected void onPause() { saveProject(); RecentBrickListManager.getInstance().saveRecentBrickList(); } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(BUNDLE_IS_UNDO_MENU_ITEM_VISIBLE, isUndoMenuItemVisible); + } @Override public void onBackPressed() { diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java index 04c786eeb1d..adee4a1dc39 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java @@ -413,7 +413,9 @@ public void onPause() { savedListViewState = listView.onSaveInstanceState(); - ((SpriteActivity) getActivity()).setUndoMenuItemVisibility(false); + if (getActivity() != null && !getActivity().isChangingConfigurations()) { + ((SpriteActivity) getActivity()).setUndoMenuItemVisibility(false); + } } @Override @@ -954,17 +956,21 @@ public void onLoadFinished(boolean success) { } loadVariables(); - refreshFragmentAfterUndo(); + if (spriteActivity != null) { spriteActivity.setUndoMenuItemVisibility(false); spriteActivity.showUndo(false); } - - Project project = ProjectManager.getInstance().getCurrentProject(); - File undoCodeFile = new File(project.getDirectory(), UNDO_CODE_XML_FILE_NAME); + + File undoCodeFile = new File(ProjectManager.getInstance().getCurrentProject().getDirectory(), UNDO_CODE_XML_FILE_NAME); if (undoCodeFile.exists() && !undoCodeFile.delete()) { Log.w(TAG, "Could not delete undo code file: " + undoCodeFile.getAbsolutePath()); } + + if (getView() == null || listView == null) { + return; + } + refreshFragmentAfterUndo(); } private void saveVariables() { @@ -1041,12 +1047,20 @@ private void refreshFragmentAfterUndo() { if (scriptFragment == null) { return; } + + Sprite currentSprite = ProjectManager.getInstance().getCurrentSprite(); + if (adapter != null && currentSprite != null) { + adapter.updateItems(currentSprite); + } + final FragmentTransaction fragmentTransaction = getParentFragmentManager().beginTransaction(); fragmentTransaction.detach(scriptFragment); fragmentTransaction.attach(scriptFragment); fragmentTransaction.commitNow(); - - if (undoBrickPosition < listView.getFirstVisiblePosition() || undoBrickPosition > listView.getLastVisiblePosition()) { + + if (listView != null + && (undoBrickPosition < listView.getFirstVisiblePosition() + || undoBrickPosition > listView.getLastVisiblePosition())) { listView.post(() -> listView.setSelection(undoBrickPosition)); } } From 1e8cdb9ffb2ca5224806a2b0e12cd94aaa8e13f6 Mon Sep 17 00:00:00 2001 From: harshsomankar123-tech Date: Tue, 14 Apr 2026 01:34:19 +0530 Subject: [PATCH 14/18] Address PR feedback and fix Checkstyle violations - Decoupled project state handling from View existence in onLoadFinished. - Improved error handling in onUndoComplete for robustness. - Fixed whitespace issues (Tabs only, no trailing whitespace) to satisfy Checkstyle. --- .../catrobat/catroid/ui/SpriteActivity.java | 2 +- .../recyclerview/fragment/ScriptFragment.java | 20 ++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/SpriteActivity.java b/catroid/src/main/java/org/catrobat/catroid/ui/SpriteActivity.java index 538b0885055..72d3d04c722 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/SpriteActivity.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/SpriteActivity.java @@ -282,7 +282,7 @@ protected void onPause() { saveProject(); RecentBrickListManager.getInstance().saveRecentBrickList(); } - + @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java index adee4a1dc39..13b5bf67b3f 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java @@ -930,6 +930,11 @@ public void loadProjectAfterUndoOption() { } catch (IOException exception) { Log.e(TAG, "Replacing project " + project.getName() + " failed.", exception); ToastUtil.showError(context, R.string.error_load_project); + SpriteActivity spriteActivity = (SpriteActivity) getActivity(); + if (spriteActivity != null && undoCodeFile.exists()) { + spriteActivity.setUndoMenuItemVisibility(true); + spriteActivity.showUndo(true); + } } } } @@ -937,9 +942,10 @@ public void loadProjectAfterUndoOption() { @Override public void onLoadFinished(boolean success) { - if (!isAdded() || getContext() == null || getView() == null || listView == null) { + if (!isAdded() || getContext() == null) { return; } + SpriteActivity spriteActivity = (SpriteActivity) getActivity(); if (!success) { Log.e(TAG, "Loading project after undo failed."); @@ -956,17 +962,17 @@ public void onLoadFinished(boolean success) { } loadVariables(); - + if (spriteActivity != null) { spriteActivity.setUndoMenuItemVisibility(false); spriteActivity.showUndo(false); } - + File undoCodeFile = new File(ProjectManager.getInstance().getCurrentProject().getDirectory(), UNDO_CODE_XML_FILE_NAME); if (undoCodeFile.exists() && !undoCodeFile.delete()) { Log.w(TAG, "Could not delete undo code file: " + undoCodeFile.getAbsolutePath()); } - + if (getView() == null || listView == null) { return; } @@ -1047,17 +1053,17 @@ private void refreshFragmentAfterUndo() { if (scriptFragment == null) { return; } - + Sprite currentSprite = ProjectManager.getInstance().getCurrentSprite(); if (adapter != null && currentSprite != null) { adapter.updateItems(currentSprite); } - + final FragmentTransaction fragmentTransaction = getParentFragmentManager().beginTransaction(); fragmentTransaction.detach(scriptFragment); fragmentTransaction.attach(scriptFragment); fragmentTransaction.commitNow(); - + if (listView != null && (undoBrickPosition < listView.getFirstVisiblePosition() || undoBrickPosition > listView.getLastVisiblePosition())) { From 15574d7f890094ccf61a4501f101472d094d9608 Mon Sep 17 00:00:00 2001 From: harshsomankar123-tech Date: Tue, 14 Apr 2026 01:42:36 +0530 Subject: [PATCH 15/18] Final formatting cleanup for Checkstyle and PR feedback --- .../catrobat/catroid/ui/SpriteActivity.java | 4 + .../recyclerview/fragment/ScriptFragment.java | 362 +++++++++--------- 2 files changed, 188 insertions(+), 178 deletions(-) diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/SpriteActivity.java b/catroid/src/main/java/org/catrobat/catroid/ui/SpriteActivity.java index 72d3d04c722..f499330634c 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/SpriteActivity.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/SpriteActivity.java @@ -189,6 +189,10 @@ public void onCreate(Bundle savedInstanceState) { int fragmentPosition = FRAGMENT_SCRIPTS; Bundle bundle = getIntent().getExtras(); + if (bundle != null) { + fragmentPosition = bundle.getInt(EXTRA_FRAGMENT_POSITION, FRAGMENT_SCRIPTS); + } + if (savedInstanceState != null) { isUndoMenuItemVisible = savedInstanceState.getBoolean(BUNDLE_IS_UNDO_MENU_ITEM_VISIBLE, false); invalidateOptionsMenu(); diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java index 13b5bf67b3f..81f66e139f1 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java @@ -187,26 +187,26 @@ public boolean onCreateActionMode(ActionMode mode, Menu menu) { switch (actionModeType) { case BACKPACK: - adapter.setCheckBoxMode(BrickAdapter.SCRIPTS_ONLY); - mode.setTitle(getString(R.string.am_backpack)); - break; + adapter.setCheckBoxMode(BrickAdapter.SCRIPTS_ONLY); + mode.setTitle(getString(R.string.am_backpack)); + break; case COPY: - adapter.setCheckBoxMode(BrickAdapter.CONNECTED_ONLY); - mode.setTitle(getString(R.string.am_copy)); - break; + adapter.setCheckBoxMode(BrickAdapter.CONNECTED_ONLY); + mode.setTitle(getString(R.string.am_copy)); + break; case DELETE: - adapter.setCheckBoxMode(BrickAdapter.ALL); - mode.setTitle(getString(R.string.am_delete)); - break; + adapter.setCheckBoxMode(BrickAdapter.ALL); + mode.setTitle(getString(R.string.am_delete)); + break; case COMMENT: - adapter.selectAllCommentedOutBricks(); - adapter.setCheckBoxMode(BrickAdapter.ALL); - mode.setTitle(getString(R.string.comment_in_out)); - break; + adapter.selectAllCommentedOutBricks(); + adapter.setCheckBoxMode(BrickAdapter.ALL); + mode.setTitle(getString(R.string.comment_in_out)); + break; case NONE: - adapter.setCheckBoxMode(NONE); - actionMode.finish(); - return false; + adapter.setCheckBoxMode(NONE); + actionMode.finish(); + return false; } return true; } @@ -220,10 +220,10 @@ public boolean onPrepareActionMode(ActionMode mode, Menu menu) { public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()) { case R.id.confirm: - handleContextualAction(); - break; + handleContextualAction(); + break; default: - return false; + return false; } return true; } @@ -242,19 +242,19 @@ private void handleContextualAction() { switch (actionModeType) { case BACKPACK: - showNewScriptGroupAlert(adapter.getSelectedItems()); - break; + showNewScriptGroupAlert(adapter.getSelectedItems()); + break; case COPY: - copy(adapter.getSelectedItems()); - break; + copy(adapter.getSelectedItems()); + break; case DELETE: - showDeleteAlert(adapter.getSelectedItems()); - break; + showDeleteAlert(adapter.getSelectedItems()); + break; case COMMENT: - toggleComments(adapter.getSelectedItems()); - break; + toggleComments(adapter.getSelectedItems()); + break; case NONE: - throw new IllegalStateException("ActionModeType not set correctly"); + throw new IllegalStateException("ActionModeType not set correctly"); } } @@ -301,9 +301,9 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa listView.cancelHighlighting(); finishActionMode(); if (activity != null && !activity.isFinishing()) { - activity.setCurrentSceneAndSprite(ProjectManager.getInstance().getCurrentlyEditedScene(), ProjectManager.getInstance().getCurrentSprite()); - activity.getSupportActionBar().setTitle(activity.createActionBarTitle()); - activity.addTabs(); + activity.setCurrentSceneAndSprite(ProjectManager.getInstance().getCurrentlyEditedScene(), ProjectManager.getInstance().getCurrentSprite()); + activity.getSupportActionBar().setTitle(activity.createActionBarTitle()); + activity.addTabs(); } activity.findViewById(R.id.toolbar).setVisibility(View.VISIBLE); }); @@ -437,25 +437,25 @@ public boolean onOptionsItemSelected(MenuItem item) { } switch (item.getItemId()) { case R.id.menu_undo: - loadProjectAfterUndoOption(); - break; + loadProjectAfterUndoOption(); + break; case R.id.backpack: - prepareBackpackActionMode(); - break; + prepareBackpackActionMode(); + break; case R.id.copy: - prepareActionMode(COPY); - break; + prepareActionMode(COPY); + break; case R.id.delete: - prepareActionMode(DELETE); - break; + prepareActionMode(DELETE); + break; case R.id.comment_in_out: - startActionMode(COMMENT); - break; + startActionMode(COMMENT); + break; case R.id.find: - scriptFinder.open(); - break; + scriptFinder.open(); + break; default: - return super.onOptionsItemSelected(item); + return super.onOptionsItemSelected(item); } return true; } @@ -518,15 +518,15 @@ public void onCategorySelected(String category) { protected void prepareActionMode(int type) { if (adapter.getCount() == 1) { switch (type) { - case COPY: - copy(adapter.getItems()); - break; - case DELETE: - delete(adapter.getItems()); - break; - default: - startActionMode(type); - break; + case COPY: + copy(adapter.getItems()); + break; + case DELETE: + delete(adapter.getItems()); + break; + default: + startActionMode(type); + break; } } else { startActionMode(type); @@ -558,19 +558,19 @@ private void startActionMode(@ActionModeType int type) { public void onSelectionChanged(int selectedItemCnt) { switch (actionModeType) { case BACKPACK: - actionMode.setTitle(getString(R.string.am_backpack) + " " + selectedItemCnt); - break; + actionMode.setTitle(getString(R.string.am_backpack) + " " + selectedItemCnt); + break; case COPY: - actionMode.setTitle(getString(R.string.am_copy) + " " + selectedItemCnt); - break; + actionMode.setTitle(getString(R.string.am_copy) + " " + selectedItemCnt); + break; case DELETE: - actionMode.setTitle(getString(R.string.am_delete) + " " + selectedItemCnt); - break; + actionMode.setTitle(getString(R.string.am_delete) + " " + selectedItemCnt); + break; case COMMENT: - actionMode.setTitle(getString(R.string.comment_in_out) + " " + selectedItemCnt); - break; + actionMode.setTitle(getString(R.string.comment_in_out) + " " + selectedItemCnt); + break; case NONE: - throw new IllegalStateException("ActionModeType not set Correctly"); + throw new IllegalStateException("ActionModeType not set Correctly"); } } @@ -600,7 +600,7 @@ public void handleAddButton() { public void addBrick(Brick brick) { try { if (!brick.getClass().equals(UserDefinedReceiverBrick.class) && !brick.getClass().equals(UserDefinedBrick.class)) { - RecentBrickListManager.getInstance().addBrick(brick.clone()); + RecentBrickListManager.getInstance().addBrick(brick.clone()); } } catch (CloneNotSupportedException e) { Log.e(TAG, Log.getStackTraceString(e)); @@ -613,11 +613,11 @@ public void addBrick(Brick brick) { public void addBrick(Brick brick, Sprite sprite, BrickAdapter brickAdapter, BrickListView brickListView) { if (brickAdapter.getCount() == 0) { if (brick instanceof ScriptBrick) { - sprite.addScript(brick.getScript()); + sprite.addScript(brick.getScript()); } else { - Script script = new StartScript(); - script.addBrick(brick); - sprite.addScript(script); + Script script = new StartScript(); + script.addBrick(brick); + sprite.addScript(script); } brickAdapter.updateItems(sprite); } else if (brickAdapter.getCount() == 1 && !(brick instanceof ScriptBrick)) { @@ -654,7 +654,7 @@ public void onBrickClick(Brick brick, int position) { new AlertDialog.Builder(getContext()).setCustomTitle(brickView).setAdapter(arrayAdapter, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - handleContextMenuItemClick(options.get(which), brick, position); + handleContextMenuItemClick(options.get(which), brick, position); } }).show(); } @@ -675,7 +675,7 @@ public static List getContextMenuItems(Brick brick) { items.add(R.string.backpack_add); if (!(brick instanceof EmptyEventBrick)) { - items.add(brick.isCommentedOut() ? R.string.brick_context_dialog_comment_in_script : R.string.brick_context_dialog_comment_out_script); + items.add(brick.isCommentedOut() ? R.string.brick_context_dialog_comment_in_script : R.string.brick_context_dialog_comment_out_script); } items.add(R.string.brick_context_dialog_copy_script); @@ -683,7 +683,7 @@ public static List getContextMenuItems(Brick brick) { items.add(R.string.brick_context_dialog_delete_script); if (brick instanceof FormulaBrick && ((FormulaBrick) brick).hasEditableFormulaField()) { - items.add(R.string.brick_context_dialog_formula_edit_brick); + items.add(R.string.brick_context_dialog_formula_edit_brick); } items.add(R.string.brick_context_dialog_move_script); @@ -691,23 +691,23 @@ public static List getContextMenuItems(Brick brick) { } else { items.add(R.string.brick_context_dialog_copy_brick); if (brick.consistsOfMultipleParts()) { - items.add(R.string.brick_context_dialog_highlight_brick_parts); + items.add(R.string.brick_context_dialog_highlight_brick_parts); } items.add(R.string.brick_context_dialog_delete_brick); items.add(brick.isCommentedOut() ? R.string.brick_context_dialog_comment_in : R.string.brick_context_dialog_comment_out); if (brick instanceof VisualPlacementBrick && ((VisualPlacementBrick) brick).areAllBrickFieldsNumbers()) { - items.add(R.string.brick_option_place_visually); + items.add(R.string.brick_option_place_visually); } if (brick instanceof FormulaBrick && ((FormulaBrick) brick).hasEditableFormulaField()) { - items.add(R.string.brick_context_dialog_formula_edit_brick); + items.add(R.string.brick_context_dialog_formula_edit_brick); } if (brick.equals(brick.getAllParts().get(0))) { - items.add(R.string.brick_context_dialog_move_brick); + items.add(R.string.brick_context_dialog_move_brick); } if (brick.hasHelpPage()) { - items.add(R.string.brick_context_dialog_help); + items.add(R.string.brick_context_dialog_help); } } return items; @@ -717,65 +717,65 @@ private void handleContextMenuItemClick(int itemId, Brick brick, int position) { showUndo(false); switch (itemId) { case R.string.backpack_add: - List bricksToPack = new ArrayList<>(); - brick.addToFlatList(bricksToPack); - showNewScriptGroupAlert(bricksToPack); - break; + List bricksToPack = new ArrayList<>(); + brick.addToFlatList(bricksToPack); + showNewScriptGroupAlert(bricksToPack); + break; case R.string.brick_context_dialog_copy_brick: case R.string.brick_context_dialog_copy_script: - try { - Brick clonedBrick = brick.getAllParts().get(0).clone(); - adapter.addItem(position, clonedBrick); - listView.startMoving(clonedBrick); - } catch (CloneNotSupportedException e) { - ToastUtil.showError(getContext(), R.string.error_copying_brick); - Log.e(TAG, Log.getStackTraceString(e)); - } - break; + try { + Brick clonedBrick = brick.getAllParts().get(0).clone(); + adapter.addItem(position, clonedBrick); + listView.startMoving(clonedBrick); + } catch (CloneNotSupportedException e) { + ToastUtil.showError(getContext(), R.string.error_copying_brick); + Log.e(TAG, Log.getStackTraceString(e)); + } + break; case R.string.brick_context_dialog_delete_brick: case R.string.brick_context_dialog_delete_script: - showDeleteAlert(brick.getAllParts()); - break; + showDeleteAlert(brick.getAllParts()); + break; case R.string.brick_context_dialog_delete_definition: - showDeleteAlert(brick.getAllParts()); - break; + showDeleteAlert(brick.getAllParts()); + break; case R.string.brick_context_dialog_comment_in: case R.string.brick_context_dialog_comment_in_script: - for (Brick brickPart : brick.getAllParts()) { - brickPart.setCommentedOut(false); - } - adapter.notifyDataSetChanged(); - break; + for (Brick brickPart : brick.getAllParts()) { + brickPart.setCommentedOut(false); + } + adapter.notifyDataSetChanged(); + break; case R.string.brick_context_dialog_comment_out: case R.string.brick_context_dialog_comment_out_script: - for (Brick brickPart : brick.getAllParts()) { - brickPart.setCommentedOut(true); - } - adapter.notifyDataSetChanged(); - break; + for (Brick brickPart : brick.getAllParts()) { + brickPart.setCommentedOut(true); + } + adapter.notifyDataSetChanged(); + break; case R.string.brick_option_place_visually: - VisualPlacementBrick visualPlacementBrick = (VisualPlacementBrick) brick; - visualPlacementBrick.placeVisually(visualPlacementBrick.getXBrickField(), visualPlacementBrick.getYBrickField()); - break; + VisualPlacementBrick visualPlacementBrick = (VisualPlacementBrick) brick; + visualPlacementBrick.placeVisually(visualPlacementBrick.getXBrickField(), visualPlacementBrick.getYBrickField()); + break; case R.string.brick_context_dialog_formula_edit_brick: - ((FormulaBrick) brick).onClick(listView); - break; + ((FormulaBrick) brick).onClick(listView); + break; case R.string.brick_context_dialog_move_brick: case R.string.brick_context_dialog_move_script: case R.string.brick_context_dialog_move_definition: - onBrickLongClick(brick, position); - break; + onBrickLongClick(brick, position); + break; case R.string.brick_context_dialog_help: - openWebViewWithHelpPage(brick); - break; + openWebViewWithHelpPage(brick); + break; case R.string.brick_context_dialog_highlight_brick_parts: - List bricksOfControlStructure = brick.getAllParts(); - List positions = new ArrayList<>(); - for (Brick brickInControlStructure : bricksOfControlStructure) { - positions.add(adapter.getPosition(brickInControlStructure)); - } - listView.highlightControlStructureBricks(positions); - break; + List bricksOfControlStructure = brick.getAllParts(); + List positions = new ArrayList<>(); + for (Brick brickInControlStructure : bricksOfControlStructure) { + positions.add(adapter.getPosition(brickInControlStructure)); + } + listView.highlightControlStructureBricks(positions); + break; } } @@ -804,15 +804,15 @@ private void showBackpackModeChooser() { CharSequence[] items = new CharSequence[] {getString(R.string.pack), getString(R.string.unpack)}; new AlertDialog.Builder(getContext()).setTitle(R.string.backpack_title).setItems(items, (dialog, which) -> { switch (which) { - case 0: - if (adapter.getItems().size() == 1) { - showNewScriptGroupAlert(adapter.getItems()); - } else { - startActionMode(BACKPACK); - } - break; - case 1: - switchToBackpack(); + case 0: + if (adapter.getItems().size() == 1) { + showNewScriptGroupAlert(adapter.getItems()); + } else { + startActionMode(BACKPACK); + } + break; + case 1: + switchToBackpack(); } }).show(); } @@ -891,11 +891,11 @@ public boolean copyProjectForUndoOption() { if (currentCodeFile.exists()) { try { - StorageOperations.transferData(currentCodeFile, undoCodeFile); - saveVariables(); - return true; + StorageOperations.transferData(currentCodeFile, undoCodeFile); + saveVariables(); + return true; } catch (IOException exception) { - Log.e(TAG, "Copying project " + project.getName() + " failed.", exception); + Log.e(TAG, "Copying project " + project.getName() + " failed.", exception); } } return false; @@ -920,21 +920,21 @@ public void loadProjectAfterUndoOption() { if (currentCodeFile.exists()) { try { - StorageOperations.transferData(undoCodeFile, currentCodeFile); - SpriteActivity spriteActivity = (SpriteActivity) getActivity(); - if (spriteActivity != null) { - spriteActivity.setUndoMenuItemVisibility(false); - spriteActivity.showUndo(false); - } - new ProjectLoader(project.getDirectory(), context).setListener(this).loadProjectAsync(); + StorageOperations.transferData(undoCodeFile, currentCodeFile); + SpriteActivity spriteActivity = (SpriteActivity) getActivity(); + if (spriteActivity != null) { + spriteActivity.setUndoMenuItemVisibility(false); + spriteActivity.showUndo(false); + } + new ProjectLoader(project.getDirectory(), context).setListener(this).loadProjectAsync(); } catch (IOException exception) { - Log.e(TAG, "Replacing project " + project.getName() + " failed.", exception); - ToastUtil.showError(context, R.string.error_load_project); - SpriteActivity spriteActivity = (SpriteActivity) getActivity(); - if (spriteActivity != null && undoCodeFile.exists()) { - spriteActivity.setUndoMenuItemVisibility(true); - spriteActivity.showUndo(true); - } + Log.e(TAG, "Replacing project " + project.getName() + " failed.", exception); + ToastUtil.showError(context, R.string.error_load_project); + SpriteActivity spriteActivity = (SpriteActivity) getActivity(); + if (spriteActivity != null && undoCodeFile.exists()) { + spriteActivity.setUndoMenuItemVisibility(true); + spriteActivity.showUndo(true); + } } } } @@ -951,8 +951,8 @@ public void onLoadFinished(boolean success) { Log.e(TAG, "Loading project after undo failed."); ToastUtil.showError(getContext(), R.string.error_load_project); if (spriteActivity != null) { - spriteActivity.setUndoMenuItemVisibility(true); - spriteActivity.showUndo(true); + spriteActivity.setUndoMenuItemVisibility(true); + spriteActivity.showUndo(true); } return; } @@ -996,25 +996,31 @@ public boolean checkVariables() { Sprite currentSprite = projectManager.getCurrentSprite(); Project project = projectManager.getCurrentProject(); + return (project != null && hasProjectVariablesChanged(project)) + || (currentSprite != null && hasSpriteVariablesChanged(currentSprite)); + } + + private boolean hasProjectVariablesChanged(Project project) { boolean changed = false; - if (project != null) { - if (savedUserVariables != null && project.getUserVariables() != null) { - changed |= project.hasUserDataChanged(project.getUserVariables(), savedUserVariables); - } - if (savedMultiplayerVariables != null && project.getMultiplayerVariables() != null) { - changed |= project.hasUserDataChanged(project.getMultiplayerVariables(), savedMultiplayerVariables); - } - if (savedUserLists != null && project.getUserLists() != null) { - changed |= project.hasUserDataChanged(project.getUserLists(), savedUserLists); - } + if (savedUserVariables != null && project.getUserVariables() != null) { + changed |= project.hasUserDataChanged(project.getUserVariables(), savedUserVariables); } - if (currentSprite != null) { - if (savedLocalUserVariables != null && currentSprite.getUserVariables() != null) { - changed |= currentSprite.hasUserDataChanged(currentSprite.getUserVariables(), savedLocalUserVariables); - } - if (savedLocalLists != null && currentSprite.getUserLists() != null) { - changed |= currentSprite.hasUserDataChanged(currentSprite.getUserLists(), savedLocalLists); - } + if (savedMultiplayerVariables != null && project.getMultiplayerVariables() != null) { + changed |= project.hasUserDataChanged(project.getMultiplayerVariables(), savedMultiplayerVariables); + } + if (savedUserLists != null && project.getUserLists() != null) { + changed |= project.hasUserDataChanged(project.getUserLists(), savedUserLists); + } + return changed; + } + + private boolean hasSpriteVariablesChanged(Sprite currentSprite) { + boolean changed = false; + if (savedLocalUserVariables != null && currentSprite.getUserVariables() != null) { + changed |= currentSprite.hasUserDataChanged(currentSprite.getUserVariables(), savedLocalUserVariables); + } + if (savedLocalLists != null && currentSprite.getUserLists() != null) { + changed |= currentSprite.hasUserDataChanged(currentSprite.getUserLists(), savedLocalLists); } return changed; } @@ -1026,21 +1032,21 @@ private void loadVariables() { if (project != null) { if (savedUserVariables != null) { - project.restoreUserDataValues(project.getUserVariables(), savedUserVariables); + project.restoreUserDataValues(project.getUserVariables(), savedUserVariables); } if (savedMultiplayerVariables != null) { - project.restoreUserDataValues(project.getMultiplayerVariables(), savedMultiplayerVariables); + project.restoreUserDataValues(project.getMultiplayerVariables(), savedMultiplayerVariables); } if (savedUserLists != null) { - project.restoreUserDataValues(project.getUserLists(), savedUserLists); + project.restoreUserDataValues(project.getUserLists(), savedUserLists); } } if (currentSprite != null) { if (savedLocalUserVariables != null) { - currentSprite.restoreUserDataValues(currentSprite.getUserVariables(), savedLocalUserVariables); + currentSprite.restoreUserDataValues(currentSprite.getUserVariables(), savedLocalUserVariables); } if (savedLocalLists != null) { - currentSprite.restoreUserDataValues(currentSprite.getUserLists(), savedLocalLists); + currentSprite.restoreUserDataValues(currentSprite.getUserLists(), savedLocalLists); } } } @@ -1065,8 +1071,8 @@ private void refreshFragmentAfterUndo() { fragmentTransaction.commitNow(); if (listView != null - && (undoBrickPosition < listView.getFirstVisiblePosition() - || undoBrickPosition > listView.getLastVisiblePosition())) { + && (undoBrickPosition < listView.getFirstVisiblePosition() + || undoBrickPosition > listView.getLastVisiblePosition())) { listView.post(() -> listView.setSelection(undoBrickPosition)); } } @@ -1087,12 +1093,12 @@ private void scrollToFocusItem() { for (int i = 0; i < listView.getAdapter().getCount(); ++i) { Object item = listView.getItemAtPosition(i); if (!(item instanceof Brick)) { - continue; + continue; } Brick brick = (Brick) item; if ((brickToFocus != null && brick == brickToFocus) || (scriptToFocus != null && brick.getScript() == scriptToFocus)) { - scrollToIndex = i; - break; + scrollToIndex = i; + break; } } if (scrollToIndex == -1) { @@ -1101,10 +1107,10 @@ private void scrollToFocusItem() { if (getActivity() != null) { int finalScrollToIndex = scrollToIndex; getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - listView.setSelection(finalScrollToIndex); - } + @Override + public void run() { + listView.setSelection(finalScrollToIndex); + } }); } scriptToFocus = null; From 0620295d3bbe38145ef7e7b0fc702aea086a8ff9 Mon Sep 17 00:00:00 2001 From: harshsomankar123-tech Date: Tue, 14 Apr 2026 01:55:26 +0530 Subject: [PATCH 16/18] Final indentation level adjustment for level 12 vs 16 issues --- .../recyclerview/fragment/ScriptFragment.java | 110 +++++++++--------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java index 81f66e139f1..15e10e3fa90 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java @@ -187,26 +187,26 @@ public boolean onCreateActionMode(ActionMode mode, Menu menu) { switch (actionModeType) { case BACKPACK: - adapter.setCheckBoxMode(BrickAdapter.SCRIPTS_ONLY); - mode.setTitle(getString(R.string.am_backpack)); - break; + adapter.setCheckBoxMode(BrickAdapter.SCRIPTS_ONLY); + mode.setTitle(getString(R.string.am_backpack)); + break; case COPY: - adapter.setCheckBoxMode(BrickAdapter.CONNECTED_ONLY); - mode.setTitle(getString(R.string.am_copy)); - break; + adapter.setCheckBoxMode(BrickAdapter.CONNECTED_ONLY); + mode.setTitle(getString(R.string.am_copy)); + break; case DELETE: - adapter.setCheckBoxMode(BrickAdapter.ALL); - mode.setTitle(getString(R.string.am_delete)); - break; + adapter.setCheckBoxMode(BrickAdapter.ALL); + mode.setTitle(getString(R.string.am_delete)); + break; case COMMENT: - adapter.selectAllCommentedOutBricks(); - adapter.setCheckBoxMode(BrickAdapter.ALL); - mode.setTitle(getString(R.string.comment_in_out)); - break; + adapter.selectAllCommentedOutBricks(); + adapter.setCheckBoxMode(BrickAdapter.ALL); + mode.setTitle(getString(R.string.comment_in_out)); + break; case NONE: - adapter.setCheckBoxMode(NONE); - actionMode.finish(); - return false; + adapter.setCheckBoxMode(NONE); + actionMode.finish(); + return false; } return true; } @@ -220,10 +220,10 @@ public boolean onPrepareActionMode(ActionMode mode, Menu menu) { public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()) { case R.id.confirm: - handleContextualAction(); - break; + handleContextualAction(); + break; default: - return false; + return false; } return true; } @@ -242,19 +242,19 @@ private void handleContextualAction() { switch (actionModeType) { case BACKPACK: - showNewScriptGroupAlert(adapter.getSelectedItems()); - break; + showNewScriptGroupAlert(adapter.getSelectedItems()); + break; case COPY: - copy(adapter.getSelectedItems()); - break; + copy(adapter.getSelectedItems()); + break; case DELETE: - showDeleteAlert(adapter.getSelectedItems()); - break; + showDeleteAlert(adapter.getSelectedItems()); + break; case COMMENT: - toggleComments(adapter.getSelectedItems()); - break; + toggleComments(adapter.getSelectedItems()); + break; case NONE: - throw new IllegalStateException("ActionModeType not set correctly"); + throw new IllegalStateException("ActionModeType not set correctly"); } } @@ -301,9 +301,9 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa listView.cancelHighlighting(); finishActionMode(); if (activity != null && !activity.isFinishing()) { - activity.setCurrentSceneAndSprite(ProjectManager.getInstance().getCurrentlyEditedScene(), ProjectManager.getInstance().getCurrentSprite()); - activity.getSupportActionBar().setTitle(activity.createActionBarTitle()); - activity.addTabs(); + activity.setCurrentSceneAndSprite(ProjectManager.getInstance().getCurrentlyEditedScene(), ProjectManager.getInstance().getCurrentSprite()); + activity.getSupportActionBar().setTitle(activity.createActionBarTitle()); + activity.addTabs(); } activity.findViewById(R.id.toolbar).setVisibility(View.VISIBLE); }); @@ -414,7 +414,7 @@ public void onPause() { savedListViewState = listView.onSaveInstanceState(); if (getActivity() != null && !getActivity().isChangingConfigurations()) { - ((SpriteActivity) getActivity()).setUndoMenuItemVisibility(false); + ((SpriteActivity) getActivity()).setUndoMenuItemVisibility(false); } } @@ -518,15 +518,15 @@ public void onCategorySelected(String category) { protected void prepareActionMode(int type) { if (adapter.getCount() == 1) { switch (type) { - case COPY: - copy(adapter.getItems()); - break; - case DELETE: - delete(adapter.getItems()); - break; - default: - startActionMode(type); - break; + case COPY: + copy(adapter.getItems()); + break; + case DELETE: + delete(adapter.getItems()); + break; + default: + startActionMode(type); + break; } } else { startActionMode(type); @@ -928,13 +928,13 @@ public void loadProjectAfterUndoOption() { } new ProjectLoader(project.getDirectory(), context).setListener(this).loadProjectAsync(); } catch (IOException exception) { - Log.e(TAG, "Replacing project " + project.getName() + " failed.", exception); - ToastUtil.showError(context, R.string.error_load_project); - SpriteActivity spriteActivity = (SpriteActivity) getActivity(); - if (spriteActivity != null && undoCodeFile.exists()) { - spriteActivity.setUndoMenuItemVisibility(true); - spriteActivity.showUndo(true); - } + Log.e(TAG, "Replacing project " + project.getName() + " failed.", exception); + ToastUtil.showError(context, R.string.error_load_project); + SpriteActivity spriteActivity = (SpriteActivity) getActivity(); + if (spriteActivity != null && undoCodeFile.exists()) { + spriteActivity.setUndoMenuItemVisibility(true); + spriteActivity.showUndo(true); + } } } } @@ -951,8 +951,8 @@ public void onLoadFinished(boolean success) { Log.e(TAG, "Loading project after undo failed."); ToastUtil.showError(getContext(), R.string.error_load_project); if (spriteActivity != null) { - spriteActivity.setUndoMenuItemVisibility(true); - spriteActivity.showUndo(true); + spriteActivity.setUndoMenuItemVisibility(true); + spriteActivity.showUndo(true); } return; } @@ -1032,21 +1032,21 @@ private void loadVariables() { if (project != null) { if (savedUserVariables != null) { - project.restoreUserDataValues(project.getUserVariables(), savedUserVariables); + project.restoreUserDataValues(project.getUserVariables(), savedUserVariables); } if (savedMultiplayerVariables != null) { - project.restoreUserDataValues(project.getMultiplayerVariables(), savedMultiplayerVariables); + project.restoreUserDataValues(project.getMultiplayerVariables(), savedMultiplayerVariables); } if (savedUserLists != null) { - project.restoreUserDataValues(project.getUserLists(), savedUserLists); + project.restoreUserDataValues(project.getUserLists(), savedUserLists); } } if (currentSprite != null) { if (savedLocalUserVariables != null) { - currentSprite.restoreUserDataValues(currentSprite.getUserVariables(), savedLocalUserVariables); + currentSprite.restoreUserDataValues(currentSprite.getUserVariables(), savedLocalUserVariables); } if (savedLocalLists != null) { - currentSprite.restoreUserDataValues(currentSprite.getUserLists(), savedLocalLists); + currentSprite.restoreUserDataValues(currentSprite.getUserLists(), savedLocalLists); } } } From c8e09646a1dfca1d097b3d2d8468ef4afa98fd21 Mon Sep 17 00:00:00 2001 From: harshsomankar123-tech Date: Tue, 14 Apr 2026 22:23:17 +0530 Subject: [PATCH 17/18] Fix PR review feedback: correct indentation in ScriptFragment.java and improve undo-after-recreation test - Fixed all indentation regressions in ScriptFragment.java: - switch/case bodies now properly nested at level 16 (4 tabs) under case labels - if/else bodies properly indented inside try/catch blocks - Runnable anonymous class body properly indented - showBackpackModeChooser switch cases properly indented - Improved testUndoAfterActivityRecreationDoesNotCrash: - Now actually clicks undo after activity recreation - Asserts the restored state (variable removed, formula reset) - Proves the original crash path is fixed and undo works post-recreation --- .../formulaeditor/FormulaEditorUndoTest.kt | 20 +- .../recyclerview/fragment/ScriptFragment.java | 212 +++++++++--------- 2 files changed, 116 insertions(+), 116 deletions(-) diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.kt b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.kt index 974f1e9a649..7c9488ec5ff 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.kt +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/formulaeditor/FormulaEditorUndoTest.kt @@ -457,6 +457,8 @@ class FormulaEditorUndoTest { onView(withId(R.id.menu_undo)) .check(matches(isDisplayed())) + assertNotNull(ProjectManager.getInstance().currentProject.getUserVariable(NEW_VARIABLE_NAME)) + InstrumentationRegistry.getInstrumentation().runOnMainSync { baseActivityTestRule.activity.recreate() } @@ -464,22 +466,20 @@ class FormulaEditorUndoTest { onBrickAtPosition(brickPosition).checkShowsText(R.string.brick_place_at) - onView(withId(R.id.brick_place_at_edit_text_x)) - .perform(click()) - onView(withText(R.string.brick_context_dialog_formula_edit_brick)) - .perform(click()) - - onFormulaEditor() - .performEnterFormula("999") + onView(withId(R.id.menu_undo)) + .perform(waitFor(isDisplayed(), waitThreshold)) - pressBack() + onView(withId(R.id.menu_undo)) + .perform(click()) onView(withId(R.id.menu_undo)) - .check(matches(isDisplayed())) + .check(doesNotExist()) + + assertNull(ProjectManager.getInstance().currentProject.getUserVariable(NEW_VARIABLE_NAME)) onBrickAtPosition(brickPosition) .onFormulaTextField(R.id.brick_place_at_edit_text_x) - .checkShowsNumber(999) + .checkShowsNumber(0) } @Category(Cat.AppUi::class, Level.Smoke::class) diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java index 15e10e3fa90..92d2e0f0a23 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java @@ -414,7 +414,7 @@ public void onPause() { savedListViewState = listView.onSaveInstanceState(); if (getActivity() != null && !getActivity().isChangingConfigurations()) { - ((SpriteActivity) getActivity()).setUndoMenuItemVisibility(false); + ((SpriteActivity) getActivity()).setUndoMenuItemVisibility(false); } } @@ -437,25 +437,25 @@ public boolean onOptionsItemSelected(MenuItem item) { } switch (item.getItemId()) { case R.id.menu_undo: - loadProjectAfterUndoOption(); - break; + loadProjectAfterUndoOption(); + break; case R.id.backpack: - prepareBackpackActionMode(); - break; + prepareBackpackActionMode(); + break; case R.id.copy: - prepareActionMode(COPY); - break; + prepareActionMode(COPY); + break; case R.id.delete: - prepareActionMode(DELETE); - break; + prepareActionMode(DELETE); + break; case R.id.comment_in_out: - startActionMode(COMMENT); - break; + startActionMode(COMMENT); + break; case R.id.find: - scriptFinder.open(); - break; + scriptFinder.open(); + break; default: - return super.onOptionsItemSelected(item); + return super.onOptionsItemSelected(item); } return true; } @@ -558,19 +558,19 @@ private void startActionMode(@ActionModeType int type) { public void onSelectionChanged(int selectedItemCnt) { switch (actionModeType) { case BACKPACK: - actionMode.setTitle(getString(R.string.am_backpack) + " " + selectedItemCnt); - break; + actionMode.setTitle(getString(R.string.am_backpack) + " " + selectedItemCnt); + break; case COPY: - actionMode.setTitle(getString(R.string.am_copy) + " " + selectedItemCnt); - break; + actionMode.setTitle(getString(R.string.am_copy) + " " + selectedItemCnt); + break; case DELETE: - actionMode.setTitle(getString(R.string.am_delete) + " " + selectedItemCnt); - break; + actionMode.setTitle(getString(R.string.am_delete) + " " + selectedItemCnt); + break; case COMMENT: - actionMode.setTitle(getString(R.string.comment_in_out) + " " + selectedItemCnt); - break; + actionMode.setTitle(getString(R.string.comment_in_out) + " " + selectedItemCnt); + break; case NONE: - throw new IllegalStateException("ActionModeType not set Correctly"); + throw new IllegalStateException("ActionModeType not set Correctly"); } } @@ -600,7 +600,7 @@ public void handleAddButton() { public void addBrick(Brick brick) { try { if (!brick.getClass().equals(UserDefinedReceiverBrick.class) && !brick.getClass().equals(UserDefinedBrick.class)) { - RecentBrickListManager.getInstance().addBrick(brick.clone()); + RecentBrickListManager.getInstance().addBrick(brick.clone()); } } catch (CloneNotSupportedException e) { Log.e(TAG, Log.getStackTraceString(e)); @@ -613,11 +613,11 @@ public void addBrick(Brick brick) { public void addBrick(Brick brick, Sprite sprite, BrickAdapter brickAdapter, BrickListView brickListView) { if (brickAdapter.getCount() == 0) { if (brick instanceof ScriptBrick) { - sprite.addScript(brick.getScript()); + sprite.addScript(brick.getScript()); } else { - Script script = new StartScript(); - script.addBrick(brick); - sprite.addScript(script); + Script script = new StartScript(); + script.addBrick(brick); + sprite.addScript(script); } brickAdapter.updateItems(sprite); } else if (brickAdapter.getCount() == 1 && !(brick instanceof ScriptBrick)) { @@ -654,7 +654,7 @@ public void onBrickClick(Brick brick, int position) { new AlertDialog.Builder(getContext()).setCustomTitle(brickView).setAdapter(arrayAdapter, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - handleContextMenuItemClick(options.get(which), brick, position); + handleContextMenuItemClick(options.get(which), brick, position); } }).show(); } @@ -675,7 +675,7 @@ public static List getContextMenuItems(Brick brick) { items.add(R.string.backpack_add); if (!(brick instanceof EmptyEventBrick)) { - items.add(brick.isCommentedOut() ? R.string.brick_context_dialog_comment_in_script : R.string.brick_context_dialog_comment_out_script); + items.add(brick.isCommentedOut() ? R.string.brick_context_dialog_comment_in_script : R.string.brick_context_dialog_comment_out_script); } items.add(R.string.brick_context_dialog_copy_script); @@ -683,7 +683,7 @@ public static List getContextMenuItems(Brick brick) { items.add(R.string.brick_context_dialog_delete_script); if (brick instanceof FormulaBrick && ((FormulaBrick) brick).hasEditableFormulaField()) { - items.add(R.string.brick_context_dialog_formula_edit_brick); + items.add(R.string.brick_context_dialog_formula_edit_brick); } items.add(R.string.brick_context_dialog_move_script); @@ -691,23 +691,23 @@ public static List getContextMenuItems(Brick brick) { } else { items.add(R.string.brick_context_dialog_copy_brick); if (brick.consistsOfMultipleParts()) { - items.add(R.string.brick_context_dialog_highlight_brick_parts); + items.add(R.string.brick_context_dialog_highlight_brick_parts); } items.add(R.string.brick_context_dialog_delete_brick); items.add(brick.isCommentedOut() ? R.string.brick_context_dialog_comment_in : R.string.brick_context_dialog_comment_out); if (brick instanceof VisualPlacementBrick && ((VisualPlacementBrick) brick).areAllBrickFieldsNumbers()) { - items.add(R.string.brick_option_place_visually); + items.add(R.string.brick_option_place_visually); } if (brick instanceof FormulaBrick && ((FormulaBrick) brick).hasEditableFormulaField()) { - items.add(R.string.brick_context_dialog_formula_edit_brick); + items.add(R.string.brick_context_dialog_formula_edit_brick); } if (brick.equals(brick.getAllParts().get(0))) { - items.add(R.string.brick_context_dialog_move_brick); + items.add(R.string.brick_context_dialog_move_brick); } if (brick.hasHelpPage()) { - items.add(R.string.brick_context_dialog_help); + items.add(R.string.brick_context_dialog_help); } } return items; @@ -717,65 +717,65 @@ private void handleContextMenuItemClick(int itemId, Brick brick, int position) { showUndo(false); switch (itemId) { case R.string.backpack_add: - List bricksToPack = new ArrayList<>(); - brick.addToFlatList(bricksToPack); - showNewScriptGroupAlert(bricksToPack); - break; + List bricksToPack = new ArrayList<>(); + brick.addToFlatList(bricksToPack); + showNewScriptGroupAlert(bricksToPack); + break; case R.string.brick_context_dialog_copy_brick: case R.string.brick_context_dialog_copy_script: - try { - Brick clonedBrick = brick.getAllParts().get(0).clone(); - adapter.addItem(position, clonedBrick); - listView.startMoving(clonedBrick); - } catch (CloneNotSupportedException e) { - ToastUtil.showError(getContext(), R.string.error_copying_brick); - Log.e(TAG, Log.getStackTraceString(e)); - } - break; + try { + Brick clonedBrick = brick.getAllParts().get(0).clone(); + adapter.addItem(position, clonedBrick); + listView.startMoving(clonedBrick); + } catch (CloneNotSupportedException e) { + ToastUtil.showError(getContext(), R.string.error_copying_brick); + Log.e(TAG, Log.getStackTraceString(e)); + } + break; case R.string.brick_context_dialog_delete_brick: case R.string.brick_context_dialog_delete_script: - showDeleteAlert(brick.getAllParts()); - break; + showDeleteAlert(brick.getAllParts()); + break; case R.string.brick_context_dialog_delete_definition: - showDeleteAlert(brick.getAllParts()); - break; + showDeleteAlert(brick.getAllParts()); + break; case R.string.brick_context_dialog_comment_in: case R.string.brick_context_dialog_comment_in_script: - for (Brick brickPart : brick.getAllParts()) { - brickPart.setCommentedOut(false); - } - adapter.notifyDataSetChanged(); - break; + for (Brick brickPart : brick.getAllParts()) { + brickPart.setCommentedOut(false); + } + adapter.notifyDataSetChanged(); + break; case R.string.brick_context_dialog_comment_out: case R.string.brick_context_dialog_comment_out_script: - for (Brick brickPart : brick.getAllParts()) { - brickPart.setCommentedOut(true); - } - adapter.notifyDataSetChanged(); - break; + for (Brick brickPart : brick.getAllParts()) { + brickPart.setCommentedOut(true); + } + adapter.notifyDataSetChanged(); + break; case R.string.brick_option_place_visually: - VisualPlacementBrick visualPlacementBrick = (VisualPlacementBrick) brick; - visualPlacementBrick.placeVisually(visualPlacementBrick.getXBrickField(), visualPlacementBrick.getYBrickField()); - break; + VisualPlacementBrick visualPlacementBrick = (VisualPlacementBrick) brick; + visualPlacementBrick.placeVisually(visualPlacementBrick.getXBrickField(), visualPlacementBrick.getYBrickField()); + break; case R.string.brick_context_dialog_formula_edit_brick: - ((FormulaBrick) brick).onClick(listView); - break; + ((FormulaBrick) brick).onClick(listView); + break; case R.string.brick_context_dialog_move_brick: case R.string.brick_context_dialog_move_script: case R.string.brick_context_dialog_move_definition: - onBrickLongClick(brick, position); - break; + onBrickLongClick(brick, position); + break; case R.string.brick_context_dialog_help: - openWebViewWithHelpPage(brick); - break; + openWebViewWithHelpPage(brick); + break; case R.string.brick_context_dialog_highlight_brick_parts: - List bricksOfControlStructure = brick.getAllParts(); - List positions = new ArrayList<>(); - for (Brick brickInControlStructure : bricksOfControlStructure) { - positions.add(adapter.getPosition(brickInControlStructure)); - } - listView.highlightControlStructureBricks(positions); - break; + List bricksOfControlStructure = brick.getAllParts(); + List positions = new ArrayList<>(); + for (Brick brickInControlStructure : bricksOfControlStructure) { + positions.add(adapter.getPosition(brickInControlStructure)); + } + listView.highlightControlStructureBricks(positions); + break; } } @@ -804,16 +804,16 @@ private void showBackpackModeChooser() { CharSequence[] items = new CharSequence[] {getString(R.string.pack), getString(R.string.unpack)}; new AlertDialog.Builder(getContext()).setTitle(R.string.backpack_title).setItems(items, (dialog, which) -> { switch (which) { - case 0: - if (adapter.getItems().size() == 1) { - showNewScriptGroupAlert(adapter.getItems()); - } else { - startActionMode(BACKPACK); + case 0: + if (adapter.getItems().size() == 1) { + showNewScriptGroupAlert(adapter.getItems()); + } else { + startActionMode(BACKPACK); + } + break; + case 1: + switchToBackpack(); } - break; - case 1: - switchToBackpack(); - } }).show(); } @@ -891,11 +891,11 @@ public boolean copyProjectForUndoOption() { if (currentCodeFile.exists()) { try { - StorageOperations.transferData(currentCodeFile, undoCodeFile); - saveVariables(); - return true; + StorageOperations.transferData(currentCodeFile, undoCodeFile); + saveVariables(); + return true; } catch (IOException exception) { - Log.e(TAG, "Copying project " + project.getName() + " failed.", exception); + Log.e(TAG, "Copying project " + project.getName() + " failed.", exception); } } return false; @@ -920,13 +920,13 @@ public void loadProjectAfterUndoOption() { if (currentCodeFile.exists()) { try { - StorageOperations.transferData(undoCodeFile, currentCodeFile); - SpriteActivity spriteActivity = (SpriteActivity) getActivity(); - if (spriteActivity != null) { - spriteActivity.setUndoMenuItemVisibility(false); - spriteActivity.showUndo(false); - } - new ProjectLoader(project.getDirectory(), context).setListener(this).loadProjectAsync(); + StorageOperations.transferData(undoCodeFile, currentCodeFile); + SpriteActivity spriteActivity = (SpriteActivity) getActivity(); + if (spriteActivity != null) { + spriteActivity.setUndoMenuItemVisibility(false); + spriteActivity.showUndo(false); + } + new ProjectLoader(project.getDirectory(), context).setListener(this).loadProjectAsync(); } catch (IOException exception) { Log.e(TAG, "Replacing project " + project.getName() + " failed.", exception); ToastUtil.showError(context, R.string.error_load_project); @@ -1093,12 +1093,12 @@ private void scrollToFocusItem() { for (int i = 0; i < listView.getAdapter().getCount(); ++i) { Object item = listView.getItemAtPosition(i); if (!(item instanceof Brick)) { - continue; + continue; } Brick brick = (Brick) item; if ((brickToFocus != null && brick == brickToFocus) || (scriptToFocus != null && brick.getScript() == scriptToFocus)) { - scrollToIndex = i; - break; + scrollToIndex = i; + break; } } if (scrollToIndex == -1) { @@ -1107,10 +1107,10 @@ private void scrollToFocusItem() { if (getActivity() != null) { int finalScrollToIndex = scrollToIndex; getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - listView.setSelection(finalScrollToIndex); - } + @Override + public void run() { + listView.setSelection(finalScrollToIndex); + } }); } scriptToFocus = null; From 84a067dd0d3f8acd2d4f9e753786e0070e520368 Mon Sep 17 00:00:00 2001 From: harshsomankar123-tech Date: Tue, 14 Apr 2026 22:30:42 +0530 Subject: [PATCH 18/18] Fix remaining Checkstyle indentation warnings in ScriptFragment.java - Fix switch closing brace indentation to match switch keyword level (12) - Fix multi-line condition continuation lines to level 16 --- .../catroid/ui/recyclerview/fragment/ScriptFragment.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java index 92d2e0f0a23..059d7bdb59e 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/recyclerview/fragment/ScriptFragment.java @@ -813,7 +813,7 @@ private void showBackpackModeChooser() { break; case 1: switchToBackpack(); - } + } }).show(); } @@ -1071,8 +1071,8 @@ private void refreshFragmentAfterUndo() { fragmentTransaction.commitNow(); if (listView != null - && (undoBrickPosition < listView.getFirstVisiblePosition() - || undoBrickPosition > listView.getLastVisiblePosition())) { + && (undoBrickPosition < listView.getFirstVisiblePosition() + || undoBrickPosition > listView.getLastVisiblePosition())) { listView.post(() -> listView.setSelection(undoBrickPosition)); } }