diff --git a/.idea/copyright/Catroid.xml b/.idea/copyright/Catroid.xml index 1f258b5084f..74e22c75105 100644 --- a/.idea/copyright/Catroid.xml +++ b/.idea/copyright/Catroid.xml @@ -1,9 +1,8 @@ - \ No newline at end of file diff --git a/catroid/build.gradle b/catroid/build.gradle index fdbb35681ac..2a73239dbd0 100644 --- a/catroid/build.gradle +++ b/catroid/build.gradle @@ -38,6 +38,7 @@ plugins { } repositories { + mavenLocal() maven { url "https://jitpack.io" } maven { url 'https://maven.fabric.io/public' } } @@ -52,7 +53,7 @@ ext { gdxVersion = "1.9.10" mockitoVersion = "2.8.47" espressoVersion = "3.1.0" - paintroidVersion = "2.4.1" + paintroidVersion = "2.5.0" } apply plugin: 'com.android.application' @@ -67,6 +68,10 @@ apply from: 'gradle/release_crowdin_tasks.gradle' apply from: 'gradle/release_fastlane_tasks.gradle' apply from: 'gradle/standalone_apk_tasks.gradle' +if(rootProject.hasProperty('paintroidLocal')) { + paintroidVersion = paintroidVersion + "-LOCAL" +} + if (rootProject.hasProperty('independent')) { println "Independent build" def today = new Date() @@ -133,8 +138,8 @@ android { targetSdkVersion 28 applicationId appId testInstrumentationRunner 'org.catrobat.catroid.runner.UiTestApplicationRunner' - versionCode 74 - versionName "0.9.70" + versionCode 75 + versionName "0.9.71" println "VersionCode is $versionCode" println "VersionName is $versionName" multiDexEnabled true @@ -433,6 +438,7 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.2.1' testImplementation 'org.reflections:reflections:0.9.11' + testImplementation 'org.json:json:20190722' androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:2.2.0' androidTestImplementation "org.mockito:mockito-core:$mockitoVersion" diff --git a/catroid/config/lint-baseline.xml b/catroid/config/lint-baseline.xml index b95405cec10..ebe60c19ed3 100644 --- a/catroid/config/lint-baseline.xml +++ b/catroid/config/lint-baseline.xml @@ -29,18 +29,73 @@ errorLine1=" public void onActivityResult(int requestCode, int resultCode, Intent data) {" errorLine2=" ~~~~~~~~~~~~~~~~"> + + + + + + + + + + + + + + + + + + + + @@ -51,7 +106,7 @@ errorLine1=" private static Context context;" errorLine2=" ~~~~~~"> @@ -62,7 +117,7 @@ errorLine1=" private Activity activity;" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -73,7 +128,7 @@ errorLine1=" private class ConnectDeviceTask extends AsyncTask<String, Void, BluetoothConnection.State> {" errorLine2=" ~~~~~~~~~~~~~~~~~"> @@ -84,7 +139,7 @@ errorLine1=" private Context context;" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -95,7 +150,7 @@ errorLine1=" checkDroneConnectionTask = new CheckDroneNetworkAvailabilityTask() {" errorLine2=" ^"> @@ -106,7 +161,7 @@ errorLine1=" private Context context;" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -117,7 +172,7 @@ errorLine1=" private static FormulaEditorEditText formulaEditorEditText;" errorLine2=" ~~~~~~"> @@ -128,7 +183,7 @@ errorLine1=" private static LinearLayout formulaEditorBrick;" errorLine2=" ~~~~~~"> @@ -139,7 +194,7 @@ errorLine1=" private static FormulaBrick formulaBrick;" errorLine2=" ~~~~~~"> @@ -150,7 +205,7 @@ errorLine1=" private Context context;" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -161,7 +216,7 @@ errorLine1=" private Context context;" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -172,7 +227,7 @@ errorLine1=" private Context context;" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -183,7 +238,7 @@ errorLine1=" private class ReadTask extends AsyncTask<UserList, Void, Void> {" errorLine2=" ~~~~~~~~"> @@ -194,7 +249,7 @@ errorLine1=" private class ReadTask extends AsyncTask<UserVariable, Void, Void> {" errorLine2=" ~~~~~~~~"> @@ -205,8 +260,8 @@ errorLine1=" private class FinishThreadAndDisposeTexturesTask extends AsyncTask<Void, Void, Void> {" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -216,7 +271,7 @@ errorLine1=" private static Context context = null;" errorLine2=" ~~~~~~"> @@ -227,7 +282,7 @@ errorLine1=" private class WriteTask extends AsyncTask<UserList, Void, Void> {" errorLine2=" ~~~~~~~~~"> @@ -238,7 +293,7 @@ errorLine1=" private class WriteTask extends AsyncTask<UserVariable, Void, Void> {" errorLine2=" ~~~~~~~~~"> @@ -247,112 +302,112 @@ id="IconColors" message="Action Bar icons should use a single gray color (`#333333` for light themes (with 60%/30% opacity for enabled/disabled), and `#FFFFFF` with opacity 80%/30% for dark themes"> + file="src\main\res\drawable-hdpi\icon_redo.png"/> + file="src\main\res\drawable-ldrtl-hdpi\icon_redo.png"/> + file="src\main\res\drawable-ldrtl-mdpi\icon_redo.png"/> + file="src\main\res\drawable-ldrtl-xhdpi\icon_redo.png"/> + file="src\main\res\drawable-mdpi\icon_redo.png"/> + file="src\main\res\drawable-xhdpi\icon_redo.png"/> + file="src\main\res\drawable-hdpi\icon_undo.png"/> + file="src\main\res\drawable-ldrtl-hdpi\icon_undo.png"/> + file="src\main\res\drawable-ldrtl-mdpi\icon_undo.png"/> + file="src\main\res\drawable-ldrtl-xhdpi\icon_undo.png"/> + file="src\main\res\drawable-mdpi\icon_undo.png"/> + file="src\main\res\drawable-xhdpi\icon_undo.png"/> + message="Missing the following drawables in `drawable-hdpi`: brick_blue_1h.9.png, brick_blue_2h.9.png, brick_blue_2h_when_9.9.png, brick_blue_3h.9.png, brick_brown_1h_when_9.9.png... (40 more)"> + file="src\main\res\drawable-hdpi"/> + message="Missing the following drawables in `drawable-mdpi`: ardrone_neg.png, ardrone_pos.png, arduino_neg.png, arduino_pos.png, brick_selection_background_bluetooth.9.png... (40 more)"> + file="src\main\res\drawable-mdpi"/> + message="Missing the following drawables in `drawable-xhdpi`: embroidery_neg.png, embroidery_pos.png, favorites_72x72.png, ic_upload_failed.png, ic_upload_success.png... (3 more)"> + file="src\main\res\drawable-xhdpi"/> + message="Missing the following drawables in `drawable-xxhdpi`: ardrone_neg.png, ardrone_pos.png, arduino_neg.png, arduino_pos.png, brick_blue_1h.9.png... (90 more)"> + file="src\main\res\drawable-xxhdpi"/> @@ -372,8 +427,8 @@ errorLine1=" public boolean onTouch(View v, MotionEvent event) {" errorLine2=" ~~~~~~~"> @@ -383,7 +438,7 @@ errorLine1=" public boolean onTouch(View v, MotionEvent event) {" errorLine2=" ~~~~~~~"> @@ -394,7 +449,7 @@ errorLine1=" btn.setOnTouchListener(otl);" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -405,7 +460,7 @@ errorLine1=" public boolean onTouch(View v, MotionEvent event) {" errorLine2=" ~~~~~~~"> @@ -416,7 +471,7 @@ errorLine1=" this.setOnTouchListener(this);" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -427,8 +482,8 @@ errorLine1=" public boolean onTouch(View view, MotionEvent event) {" errorLine2=" ~~~~~~~"> @@ -438,7 +493,7 @@ errorLine1=" public boolean onTouchEvent(MotionEvent e) {" errorLine2=" ~~~~~~~~~~~~"> @@ -449,7 +504,7 @@ errorLine1=" frameLayout.setOnTouchListener(this);" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -460,7 +515,7 @@ errorLine1=" public boolean onTouch(View view, MotionEvent event) {" errorLine2=" ~~~~~~~"> @@ -471,7 +526,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -482,7 +537,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -493,7 +548,7 @@ errorLine1=" <ImageButton" errorLine2=" ~~~~~~~~~~~"> @@ -504,7 +559,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -515,7 +570,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -526,7 +581,7 @@ errorLine1=" <ImageButton" errorLine2=" ~~~~~~~~~~~"> @@ -537,7 +592,7 @@ errorLine1=" <ImageButton" errorLine2=" ~~~~~~~~~~~"> @@ -548,7 +603,7 @@ errorLine1=" <ImageButton" errorLine2=" ~~~~~~~~~~~"> @@ -559,7 +614,7 @@ errorLine1=" <ImageButton" errorLine2=" ~~~~~~~~~~~"> @@ -570,7 +625,7 @@ errorLine1=" <ImageButton" errorLine2=" ~~~~~~~~~~~"> @@ -581,7 +636,7 @@ errorLine1=" <ImageButton" errorLine2=" ~~~~~~~~~~~"> @@ -592,7 +647,7 @@ errorLine1=" <ImageButton" errorLine2=" ~~~~~~~~~~~"> @@ -603,7 +658,7 @@ errorLine1=" <ImageButton" errorLine2=" ~~~~~~~~~~~"> @@ -614,7 +669,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -625,7 +680,7 @@ errorLine1=" <ImageButton" errorLine2=" ~~~~~~~~~~~"> @@ -636,7 +691,7 @@ errorLine1=" <ImageButton" errorLine2=" ~~~~~~~~~~~"> @@ -647,7 +702,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -658,8 +713,8 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -669,8 +724,8 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -680,29 +735,18 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> - - - - @@ -713,7 +757,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -724,7 +768,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -735,7 +779,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -746,7 +790,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -757,7 +801,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -768,7 +812,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -779,7 +823,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -790,7 +834,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -801,7 +845,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -812,7 +856,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -823,7 +867,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -834,7 +878,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -845,7 +889,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -856,7 +900,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -867,7 +911,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -878,7 +922,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -889,7 +933,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -900,7 +944,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -911,7 +955,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -922,7 +966,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -933,7 +977,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -944,29 +988,18 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> - - - - @@ -977,7 +1010,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -988,7 +1021,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -999,7 +1032,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1010,7 +1043,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1021,7 +1054,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1032,7 +1065,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1043,7 +1076,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1054,7 +1087,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1065,7 +1098,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1076,7 +1109,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1087,7 +1120,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1098,7 +1131,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1109,7 +1142,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1120,7 +1153,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1131,7 +1164,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1142,7 +1175,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1153,7 +1186,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1164,7 +1197,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1175,7 +1208,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1186,7 +1219,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1197,7 +1230,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1208,7 +1241,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1219,7 +1252,7 @@ errorLine1=" android:paddingStart="@dimen/material_design_spacing_small"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1230,7 +1263,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1241,7 +1274,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1252,7 +1285,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1263,7 +1296,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1274,7 +1307,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1285,7 +1318,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1296,7 +1329,7 @@ errorLine1=" android:paddingStart="@dimen/material_design_spacing_small"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1307,7 +1340,7 @@ errorLine1=" android:paddingStart="@dimen/material_design_spacing_small"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1318,7 +1351,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1329,7 +1362,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1340,7 +1373,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1351,7 +1384,7 @@ errorLine1=" android:paddingStart="5dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1362,7 +1395,7 @@ errorLine1=" android:paddingStart="@dimen/device_text_view_padding"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1373,7 +1406,7 @@ errorLine1=" android:paddingStart="@dimen/device_text_view_padding"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1384,7 +1417,7 @@ errorLine1=" android:paddingStart="@dimen/material_design_spacing_large"/>" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1395,7 +1428,7 @@ errorLine1=" android:paddingStart="@dimen/material_design_spacing_large"/>" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1406,7 +1439,7 @@ errorLine1=" android:paddingStart="@dimen/material_design_spacing_large"/>" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1417,7 +1450,7 @@ errorLine1=" android:paddingStart="5dp">" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1428,7 +1461,7 @@ errorLine1=" android:paddingStart="@dimen/material_design_spacing_large"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1439,7 +1472,7 @@ errorLine1=" android:paddingStart="@dimen/material_design_spacing_large"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1450,7 +1483,7 @@ errorLine1=" android:paddingEnd="@dimen/material_design_spacing_small" />" errorLine2=" ~~~~~~~~~~~~~~~~~~"> @@ -1461,7 +1494,7 @@ errorLine1=" android:paddingEnd="@dimen/details_spacing"" errorLine2=" ~~~~~~~~~~~~~~~~~~"> @@ -1472,7 +1505,7 @@ errorLine1=" android:paddingEnd="@dimen/details_spacing"" errorLine2=" ~~~~~~~~~~~~~~~~~~"> diff --git a/catroid/src/androidTest/assets/catrobatTestRunnerTests/fail/testFailListDoubleNotEqual.catrobat b/catroid/src/androidTest/assets/catrobatTestRunnerTests/fail/testFailListDoubleNotEqual.catrobat new file mode 100644 index 00000000000..ca35f253045 Binary files /dev/null and b/catroid/src/androidTest/assets/catrobatTestRunnerTests/fail/testFailListDoubleNotEqual.catrobat differ diff --git a/catroid/src/androidTest/assets/catrobatTestRunnerTests/fail/testFailListMismatchingTypes.catrobat b/catroid/src/androidTest/assets/catrobatTestRunnerTests/fail/testFailListMismatchingTypes.catrobat new file mode 100644 index 00000000000..367c8fd2bd4 Binary files /dev/null and b/catroid/src/androidTest/assets/catrobatTestRunnerTests/fail/testFailListMismatchingTypes.catrobat differ diff --git a/catroid/src/androidTest/assets/catrobatTestRunnerTests/fail/testFailListStringNotEqual.catrobat b/catroid/src/androidTest/assets/catrobatTestRunnerTests/fail/testFailListStringNotEqual.catrobat new file mode 100644 index 00000000000..b4894efd296 Binary files /dev/null and b/catroid/src/androidTest/assets/catrobatTestRunnerTests/fail/testFailListStringNotEqual.catrobat differ diff --git a/catroid/src/androidTest/assets/catrobatTestRunnerTests/success/testSuccessListEqual.catrobat b/catroid/src/androidTest/assets/catrobatTestRunnerTests/success/testSuccessListEqual.catrobat new file mode 100644 index 00000000000..0ecfb8bcb7b Binary files /dev/null and b/catroid/src/androidTest/assets/catrobatTestRunnerTests/success/testSuccessListEqual.catrobat differ diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/catrobattestrunner/CatrobatTestRunnerTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/catrobattestrunner/CatrobatTestRunnerTest.java index 3b04293ae41..41b72ee338c 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/catrobattestrunner/CatrobatTestRunnerTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/catrobattestrunner/CatrobatTestRunnerTest.java @@ -50,19 +50,28 @@ public void testStringEqual() throws Exception { @Test public void testFailMismatchingTypes() throws Exception { - exception.expectMessage("expected:<5.0> but was:"); + exception.expectMessage("AssertEqualsError\n" + + "expected: <5.0>\n" + + "actual: \n" + + "deviation: ^"); testAsset("testFailMismatchingTypes.catrobat", "catrobatTestRunnerTests/fail"); } @Test public void testFailStringNotEqual() throws Exception { - exception.expectMessage("expected: but was:"); + exception.expectMessage("AssertEqualsError\n" + + "expected: \n" + + "actual: \n" + + "deviation: ^"); testAsset("testFailStringNotEqual.catrobat", "catrobatTestRunnerTests/fail"); } @Test public void testFailDoubleNotEqual() throws Exception { - exception.expectMessage("expected:<1.0> but was:<1.1>"); + exception.expectMessage("AssertEqualsError\n" + + "expected: <1.0>\n" + + "actual: <1.1>\n" + + "deviation: --^"); testAsset("testFailDoubleNotEqual.catrobat", "catrobatTestRunnerTests/fail"); } @@ -78,6 +87,57 @@ public void testTapBrick() throws Exception { testAsset("testTapBrick.catrobat", "catrobatTestRunnerTests/success"); } + @Test + public void testSuccessListEqual() throws Exception { + + testAsset("testSuccessListEqual.catrobat", "catrobatTestRunnerTests/success"); + } + + @Test + public void testFailListDoubleNotEqual() throws Exception { + exception.expectMessage("AssertUserListError\n" + + "position: 1\n" + + "expected: <1.1>\n" + + "actual: <1.2>\n" + + "deviation: --^\n" + + "\n" + + "position: 2\n" + + "expected: <5.2>\n" + + "actual: <5>\n" + + "deviation: -^\n"); + testAsset("testFailListDoubleNotEqual.catrobat", "catrobatTestRunnerTests/fail"); + } + + @Test + public void testFailListStringNotEqual() throws Exception { + exception.expectMessage("AssertUserListError\n" + + "position: 0\n" + + "expected: \n" + + "actual: \n" + + "deviation: ^\n" + + "\n" + + "position: 1\n" + + "expected: \n" + + "actual: \n" + + "deviation: ^\n"); + testAsset("testFailListStringNotEqual.catrobat", "catrobatTestRunnerTests/fail"); + } + + @Test + public void testFailListMismatchingTypes() throws Exception { + exception.expectMessage("AssertUserListError\n" + + "position: 0\n" + + "expected: \n" + + "actual: <125.0>\n" + + "deviation: ^\n" + + "\n" + + "position: 1\n" + + "expected: <12.3>\n" + + "actual: \n" + + "deviation: ^\n"); + testAsset("testFailListMismatchingTypes.catrobat", "catrobatTestRunnerTests/fail"); + } + private void testAsset(String assetName, String assetPath) throws Exception { catrobatTestRunner.assetName = assetName; catrobatTestRunner.assetPath = assetPath; diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/test/BricksHelpUrlTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/test/BricksHelpUrlTest.java index 7553e0fbee1..f2d652da9f9 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/test/BricksHelpUrlTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/test/BricksHelpUrlTest.java @@ -27,7 +27,6 @@ import org.catrobat.catroid.ProjectManager; import org.catrobat.catroid.content.Project; import org.catrobat.catroid.content.bricks.Brick; -import org.catrobat.catroid.content.bricks.UserDefinedBrick; import org.catrobat.catroid.ui.fragment.CategoryBricksFactory; import org.junit.Before; import org.junit.Test; @@ -105,6 +104,8 @@ public class BricksHelpUrlTest { "https://wiki.catrobat.org/bin/view/Documentation/Brick%20Documentation/Jumping Sumo%20Bricks/#JumpingSumoTurnBrick"); brickToHelpUrlMapping.put("org.catrobat.catroid.content.bricks.RepeatBrick", "https://wiki.catrobat.org/bin/view/Documentation/Brick%20Documentation/Control%20Bricks/#RepeatBrick"); + brickToHelpUrlMapping.put("org.catrobat.catroid.content.bricks.ForVariableFromToBrick", + "https://wiki.catrobat.org/bin/view/Documentation/Brick%20Documentation/Control%20Bricks/#ForVariableFromToBrick"); brickToHelpUrlMapping.put("org.catrobat.catroid.content.bricks.SayBubbleBrick", "https://wiki.catrobat.org/bin/view/Documentation/Brick%20Documentation/Looks%20Bricks/#SayBubbleBrick"); brickToHelpUrlMapping.put("org.catrobat.catroid.content.bricks.SetBrightnessBrick", @@ -173,6 +174,8 @@ public class BricksHelpUrlTest { "https://wiki.catrobat.org/bin/view/Documentation/Brick%20Documentation/Event%20Bricks/#BroadcastBrick"); brickToHelpUrlMapping.put("org.catrobat.catroid.content.bricks.FlashBrick", "https://wiki.catrobat.org/bin/view/Documentation/Brick%20Documentation/Looks%20Bricks/#FlashBrick"); + brickToHelpUrlMapping.put("org.catrobat.catroid.content.bricks.StopSoundBrick", + "https://wiki.catrobat.org/bin/view/Documentation/Brick%20Documentation/Sound%20Bricks/#StopSoundBrick"); brickToHelpUrlMapping.put("org.catrobat.catroid.content.bricks.StopAllSoundsBrick", "https://wiki.catrobat.org/bin/view/Documentation/Brick%20Documentation/Sound%20Bricks/#StopAllSoundsBrick"); brickToHelpUrlMapping.put("org.catrobat.catroid.content.bricks.WriteListOnDeviceBrick", @@ -363,6 +366,10 @@ public class BricksHelpUrlTest { "https://wiki.catrobat.org/bin/view/Documentation/Brick%20Documentation/Motion%20Bricks/#VibrationBrick"); brickToHelpUrlMapping.put("org.catrobat.catroid.content.bricks.WriteVariableOnDeviceBrick", "https://wiki.catrobat.org/bin/view/Documentation/Brick%20Documentation/Data%20Bricks/#WriteVariableOnDeviceBrick"); + brickToHelpUrlMapping.put("org.catrobat.catroid.content.bricks.WriteVariableToFileBrick", + "https://wiki.catrobat.org/bin/view/Documentation/Brick%20Documentation/Data%20Bricks/#WriteVariableToFileBrick"); + brickToHelpUrlMapping.put("org.catrobat.catroid.content.bricks.ReadVariableFromFileBrick", + "https://wiki.catrobat.org/bin/view/Documentation/Brick%20Documentation/Data%20Bricks/#ReadVariableFromFileBrick"); brickToHelpUrlMapping.put("org.catrobat.catroid.content.bricks.JumpingSumoAnimationsBrick", "https://wiki.catrobat.org/bin/view/Documentation/Brick%20Documentation/Jumping Sumo%20Bricks/#JumpingSumoAnimationsBrick"); brickToHelpUrlMapping.put("org.catrobat.catroid.content.bricks.SetPenColorBrick", @@ -395,8 +402,12 @@ public class BricksHelpUrlTest { "https://wiki.catrobat.org/bin/view/Documentation/Brick%20Documentation/Embroidery%20Bricks/#TripleStitchBrick"); brickToHelpUrlMapping.put("org.catrobat.catroid.content.bricks.UserDefinedBrick", "https://wiki.catrobat.org/bin/view/Documentation/Brick%20Documentation/Your bricks%20Bricks/#UserDefinedBrick"); + brickToHelpUrlMapping.put("org.catrobat.catroid.content.bricks.UserDefinedReceiverBrick", + "https://wiki.catrobat.org/bin/view/Documentation/Brick%20Documentation/Your bricks%20Bricks/#UserDefinedReceiverBrick"); brickToHelpUrlMapping.put("org.catrobat.catroid.content.bricks.StoreCSVIntoUserListBrick", "https://wiki.catrobat.org/bin/view/Documentation/Brick%20Documentation/Data%20Bricks/#StoreCSVIntoUserListBrick"); + brickToHelpUrlMapping.put("org.catrobat.catroid.content.bricks.AssertUserListsBrick", + "https://wiki.catrobat.org/bin/view/Documentation/Brick%20Documentation/Testing%20Bricks/#AssertUserListsBrick"); } @Parameterized.Parameters(name = "{0}") @@ -452,9 +463,6 @@ public void testBrickHelpUrl() throws IllegalAccessException, String category = new CategoryBricksFactory().getBrickCategory(brick, false, InstrumentationRegistry.getInstrumentation().getTargetContext()); String brickHelpUrl = brick.getHelpUrl(category); - if (brick instanceof UserDefinedBrick) { - brickHelpUrl = brick.getHelpUrl("Your bricks"); - } assertEquals(brickToHelpUrlMapping.get(simpleName), brickHelpUrl); } diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/test/content/actions/PlaySoundActionTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/test/content/actions/PlaySoundActionTest.java index 8f38ef9443b..07a240ecbcc 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/test/content/actions/PlaySoundActionTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/test/content/actions/PlaySoundActionTest.java @@ -22,17 +22,15 @@ */ package org.catrobat.catroid.test.content.actions; -import android.media.MediaPlayer; - import com.badlogic.gdx.scenes.scene2d.Action; import org.catrobat.catroid.ProjectManager; import org.catrobat.catroid.common.SoundInfo; import org.catrobat.catroid.content.ActionFactory; +import org.catrobat.catroid.content.MediaPlayerWithSoundDetails; import org.catrobat.catroid.content.Project; import org.catrobat.catroid.content.SingleSprite; import org.catrobat.catroid.content.Sprite; -import org.catrobat.catroid.io.ResourceImporter; import org.catrobat.catroid.io.SoundManager; import org.catrobat.catroid.io.XstreamSerializer; import org.catrobat.catroid.test.R; @@ -48,17 +46,15 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.platform.app.InstrumentationRegistry; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; -import static org.catrobat.catroid.common.Constants.SOUND_DIRECTORY_NAME; - @RunWith(AndroidJUnit4.class) public class PlaySoundActionTest { private final SoundManager soundManager = SoundManager.getInstance(); private File soundFile; + private Project project; @Before public void setUp() throws Exception { @@ -83,25 +79,28 @@ public void testPlaySound() { Action action = factory.createPlaySoundAction(testSprite, soundInfo); action.act(1.0f); - List mediaPlayers = soundManager.getMediaPlayers(); + List mediaPlayers = soundManager.getMediaPlayers(); assertEquals(1, mediaPlayers.size()); assertTrue(mediaPlayers.get(0).isPlaying()); } @Test - public void testPlaySimultaneousSounds() { + public void testPlaySimultaneousSounds() throws IOException { + File soundFile2 = TestUtils.createSoundFile(project, R.raw.testsoundui, "soundTest.mp3"); Sprite testSprite = new SingleSprite("testSprite"); SoundInfo soundInfo = createSoundInfo(soundFile); + SoundInfo soundInfo2 = createSoundInfo(soundFile2); testSprite.getSoundList().add(soundInfo); + testSprite.getSoundList().add(soundInfo2); ActionFactory factory = testSprite.getActionFactory(); Action playSoundAction1 = factory.createPlaySoundAction(testSprite, soundInfo); - Action playSoundAction2 = factory.createPlaySoundAction(testSprite, soundInfo); + Action playSoundAction2 = factory.createPlaySoundAction(testSprite, soundInfo2); playSoundAction1.act(1.0f); playSoundAction2.act(1.0f); - List mediaPlayers = soundManager.getMediaPlayers(); + List mediaPlayers = soundManager.getMediaPlayers(); assertEquals(2, mediaPlayers.size()); assertTrue(mediaPlayers.get(0).isPlaying()); assertTrue(mediaPlayers.get(1).isPlaying()); @@ -109,16 +108,11 @@ public void testPlaySimultaneousSounds() { private void createTestProject() throws IOException { String projectName = "testProject"; - Project project = new Project(ApplicationProvider.getApplicationContext(), projectName); + project = new Project(ApplicationProvider.getApplicationContext(), projectName); XstreamSerializer.getInstance().saveProject(project); ProjectManager.getInstance().setCurrentProject(project); - int soundFileId = R.raw.testsound; - soundFile = ResourceImporter.createSoundFileFromResourcesInDirectory( - InstrumentationRegistry.getInstrumentation().getContext().getResources(), - soundFileId, - new File(project.getDefaultScene().getDirectory(), SOUND_DIRECTORY_NAME), - "soundTest.mp3"); + soundFile = TestUtils.createSoundFile(project, R.raw.testsound, "soundTest.mp3"); } private SoundInfo createSoundInfo(File soundFile) { diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/test/content/actions/StopAllSoundsActionTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/test/content/actions/StopAllSoundsActionTest.java index 8f1d2cef103..ac37ec4b5d9 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/test/content/actions/StopAllSoundsActionTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/test/content/actions/StopAllSoundsActionTest.java @@ -22,17 +22,15 @@ */ package org.catrobat.catroid.test.content.actions; -import android.media.MediaPlayer; - import com.badlogic.gdx.scenes.scene2d.Action; import org.catrobat.catroid.ProjectManager; import org.catrobat.catroid.common.SoundInfo; import org.catrobat.catroid.content.ActionFactory; +import org.catrobat.catroid.content.MediaPlayerWithSoundDetails; import org.catrobat.catroid.content.Project; import org.catrobat.catroid.content.SingleSprite; import org.catrobat.catroid.content.Sprite; -import org.catrobat.catroid.io.ResourceImporter; import org.catrobat.catroid.io.SoundManager; import org.catrobat.catroid.io.XstreamSerializer; import org.catrobat.catroid.test.R; @@ -48,18 +46,16 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.platform.app.InstrumentationRegistry; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; -import static org.catrobat.catroid.common.Constants.SOUND_DIRECTORY_NAME; - @RunWith(AndroidJUnit4.class) public class StopAllSoundsActionTest { private final SoundManager soundManager = SoundManager.getInstance(); private File soundFile; + private Project project; @Before public void setUp() throws Exception { @@ -75,12 +71,12 @@ public void tearDown() throws Exception { } @Test - public void testStopOneSound() throws Exception { + public void testStopOneSound() { Sprite testSprite = new SingleSprite("testSprite"); SoundInfo soundInfo = createSoundInfo(soundFile); testSprite.getSoundList().add(soundInfo); - List mediaPlayers = soundManager.getMediaPlayers(); + List mediaPlayers = soundManager.getMediaPlayers(); ActionFactory factory = testSprite.getActionFactory(); Action playSoundAction = factory.createPlaySoundAction(testSprite, soundInfo); @@ -98,18 +94,21 @@ public void testStopOneSound() throws Exception { @Test public void testStopSimultaneousPlayingSounds() throws Exception { + File soundFile2 = TestUtils.createSoundFile(project, R.raw.testsoundui, "soundTest.mp3"); Sprite testSprite = new SingleSprite("testSprite"); SoundInfo soundInfo = createSoundInfo(soundFile); + SoundInfo soundInfo2 = createSoundInfo(soundFile2); testSprite.getSoundList().add(soundInfo); + testSprite.getSoundList().add(soundInfo2); ActionFactory factory = testSprite.getActionFactory(); Action playSoundAction1 = factory.createPlaySoundAction(testSprite, soundInfo); - Action playSoundAction2 = factory.createPlaySoundAction(testSprite, soundInfo); + Action playSoundAction2 = factory.createPlaySoundAction(testSprite, soundInfo2); playSoundAction1.act(1.0f); playSoundAction2.act(1.0f); - List mediaPlayers = soundManager.getMediaPlayers(); + List mediaPlayers = soundManager.getMediaPlayers(); assertEquals(2, mediaPlayers.size()); assertTrue(mediaPlayers.get(0).isPlaying()); assertTrue(mediaPlayers.get(1).isPlaying()); @@ -124,16 +123,11 @@ public void testStopSimultaneousPlayingSounds() throws Exception { private void createTestProject() throws IOException { final String projectName = TestUtils.DEFAULT_TEST_PROJECT_NAME; - Project project = new Project(ApplicationProvider.getApplicationContext(), projectName); + project = new Project(ApplicationProvider.getApplicationContext(), projectName); XstreamSerializer.getInstance().saveProject(project); ProjectManager.getInstance().setCurrentProject(project); - int soundFileId = R.raw.testsound; - soundFile = ResourceImporter.createSoundFileFromResourcesInDirectory( - InstrumentationRegistry.getInstrumentation().getContext().getResources(), - soundFileId, - new File(project.getDefaultScene().getDirectory(), SOUND_DIRECTORY_NAME), - "soundTest.mp3"); + soundFile = TestUtils.createSoundFile(project, R.raw.testsound, "soundTest.mp3"); } private SoundInfo createSoundInfo(File soundFile) { diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/test/content/actions/StopSoundActionTest.kt b/catroid/src/androidTest/java/org/catrobat/catroid/test/content/actions/StopSoundActionTest.kt new file mode 100644 index 00000000000..85c26168177 --- /dev/null +++ b/catroid/src/androidTest/java/org/catrobat/catroid/test/content/actions/StopSoundActionTest.kt @@ -0,0 +1,98 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2020 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.test.content.actions + +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.catrobat.catroid.common.SoundInfo +import org.catrobat.catroid.content.Project +import org.catrobat.catroid.content.SingleSprite +import org.catrobat.catroid.content.Sprite +import org.catrobat.catroid.io.SoundManager +import org.catrobat.catroid.test.R +import org.catrobat.catroid.test.utils.TestUtils +import org.catrobat.catroid.test.utils.TestUtils.createSoundFile +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import java.io.File + +@RunWith(AndroidJUnit4::class) +class StopSoundActionTest { + private val soundManager = SoundManager.getInstance() + lateinit var soundFile: File + lateinit var project: Project + lateinit var sprite: Sprite + + @Before + fun setUp() { + project = Project( + ApplicationProvider.getApplicationContext(), + TestUtils.DEFAULT_TEST_PROJECT_NAME + ) + soundFile = createSoundFile(project, R.raw.testsound, "soundTest.mp3") + sprite = SingleSprite(TestUtils.DEFAULT_TEST_SPRITE_NAME) + } + + @Test + fun testStopOneSound() { + val soundInfo = createSoundInfo(soundFile) + sprite.soundList.add(soundInfo) + + assertTrue(sprite.actionFactory.createPlaySoundAction(sprite, soundInfo).act(1.0f)) + assertEquals(1, soundManager.mediaPlayers.size) + assertTrue(soundManager.mediaPlayers[0].isPlaying) + + assertTrue(sprite.actionFactory.createStopSoundAction(sprite, soundInfo).act(1.0f)) + assertFalse(soundManager.mediaPlayers[0].isPlaying) + } + + @Test + fun testStopSimultaneousPlayingSounds() { + val soundInfo1 = createSoundInfo(soundFile) + val soundInfo2 = createSoundInfo(createSoundFile(project, R.raw.testsoundui, soundFile.name)) + sprite.soundList.add(soundInfo1) + sprite.soundList.add(soundInfo2) + + assertTrue(sprite.actionFactory.createPlaySoundAction(sprite, soundInfo1).act(1.0f)) + assertTrue(sprite.actionFactory.createPlaySoundAction(sprite, soundInfo2).act(1.0f)) + assertEquals(2, soundManager.mediaPlayers.size) + assertTrue(soundManager.mediaPlayers[0].isPlaying) + assertTrue(soundManager.mediaPlayers[1].isPlaying) + + assertTrue(sprite.actionFactory.createStopSoundAction(sprite, soundInfo1).act(1.0f)) + assertFalse(soundManager.mediaPlayers[0].isPlaying) + assertTrue(soundManager.mediaPlayers[1].isPlaying) + } + + @After + fun tearDown() { + soundManager.clear() + } + + private fun createSoundInfo(soundFile: File) = SoundInfo().also { it.file = soundFile } +} diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/test/content/actions/WaitForSoundActionTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/test/content/actions/WaitForSoundActionTest.java new file mode 100644 index 00000000000..e0957095fe9 --- /dev/null +++ b/catroid/src/androidTest/java/org/catrobat/catroid/test/content/actions/WaitForSoundActionTest.java @@ -0,0 +1,121 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2020 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.test.content.actions; + +import org.catrobat.catroid.ProjectManager; +import org.catrobat.catroid.content.ActionFactory; +import org.catrobat.catroid.content.Project; +import org.catrobat.catroid.content.SoundFilePathWithSprite; +import org.catrobat.catroid.content.Sprite; +import org.catrobat.catroid.content.actions.WaitForSoundAction; +import org.catrobat.catroid.formulaeditor.Formula; +import org.catrobat.catroid.io.SoundManager; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mockito; + +import java.util.HashSet; +import java.util.Set; + +import androidx.test.core.app.ApplicationProvider; + +import static junit.framework.TestCase.assertEquals; + +import static org.junit.Assert.assertNotEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(JUnit4.class) +public class WaitForSoundActionTest { + + private static ProjectManager projectManager; + private static Project project; + private WaitForSoundAction action; + private static final float SOUND_DURATION = 2.0f; + private static final String PATH_TO_SOUND_FILE = "soundFilePath"; + private Set pathSet; + + @BeforeClass + public static void setUpProjectManager() { + project = new Project(ApplicationProvider.getApplicationContext(), "projectName"); + projectManager = ProjectManager.getInstance(); + } + + @Before + public void setUp() { + createProject(this.getClass().getSimpleName()); + } + + @Test + public void testWaitDurationSameAsSoundDuration() { + createActionWithStoppedSoundFilePath(PATH_TO_SOUND_FILE); + action.act(0.1f); + assertEquals(SOUND_DURATION, action.getDuration()); + } + + @Test + public void testStopWaitWhenSameSoundStartsPlaying() { + createActionWithStoppedSoundFilePath(PATH_TO_SOUND_FILE); + action.act(0.1f); + assertEquals(action.getTime(), action.getDuration()); + } + + @Test + public void testWaitWhenDifferentSoundsStartsPlaying() { + createActionWithStoppedSoundFilePath(PATH_TO_SOUND_FILE + "test"); + action.act(0.1f); + assertNotEquals(action.getTime(), action.getDuration(), 0.0); + } + + @Test + public void testWaitWhenOtherSpriteStoppedSameSound() { + createActionWithStoppedSoundFilePath(PATH_TO_SOUND_FILE); + pathSet.clear(); + pathSet.add(new SoundFilePathWithSprite(PATH_TO_SOUND_FILE, mock(Sprite.class))); + action.act(0.1f); + assertNotEquals(action.getTime(), action.getDuration(), 0.0); + } + + private void createProject(String projectName) { + project = new Project(ApplicationProvider.getApplicationContext(), projectName); + projectManager.setCurrentProject(project); + projectManager.setCurrentSprite(project.getDefaultScene().getBackgroundSprite()); + } + + private void createActionWithStoppedSoundFilePath(String soundPath) { + SoundManager soundManager = Mockito.mock(SoundManager.class); + pathSet = new HashSet<>(); + pathSet.add(new SoundFilePathWithSprite(soundPath, project.getDefaultScene().getBackgroundSprite())); + when(soundManager.getRecentlyStoppedSoundfilePaths()).thenReturn(pathSet); + when(soundManager.getDurationOfSoundFile(anyString())).thenReturn(SOUND_DURATION * 1000); + action = (WaitForSoundAction) (new ActionFactory()).createWaitForSoundAction( + project.getDefaultScene().getBackgroundSprite(), new Formula(SOUND_DURATION), + PATH_TO_SOUND_FILE); + action.setSoundManager(soundManager); + } +} diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/test/content/bricks/BrickCategoryTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/test/content/bricks/BrickCategoryTest.java index 263d687657e..87c8030f6b0 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/test/content/bricks/BrickCategoryTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/test/content/bricks/BrickCategoryTest.java @@ -37,6 +37,7 @@ import org.catrobat.catroid.content.bricks.AskBrick; import org.catrobat.catroid.content.bricks.AskSpeechBrick; import org.catrobat.catroid.content.bricks.AssertEqualsBrick; +import org.catrobat.catroid.content.bricks.AssertUserListsBrick; import org.catrobat.catroid.content.bricks.Brick; import org.catrobat.catroid.content.bricks.BroadcastBrick; import org.catrobat.catroid.content.bricks.BroadcastReceiverBrick; @@ -73,6 +74,7 @@ import org.catrobat.catroid.content.bricks.DroneTurnRightBrick; import org.catrobat.catroid.content.bricks.FinishStageBrick; import org.catrobat.catroid.content.bricks.FlashBrick; +import org.catrobat.catroid.content.bricks.ForVariableFromToBrick; import org.catrobat.catroid.content.bricks.ForeverBrick; import org.catrobat.catroid.content.bricks.GlideToBrick; import org.catrobat.catroid.content.bricks.GoNStepsBackBrick; @@ -126,6 +128,7 @@ import org.catrobat.catroid.content.bricks.RaspiSendDigitalValueBrick; import org.catrobat.catroid.content.bricks.ReadListFromDeviceBrick; import org.catrobat.catroid.content.bricks.ReadVariableFromDeviceBrick; +import org.catrobat.catroid.content.bricks.ReadVariableFromFileBrick; import org.catrobat.catroid.content.bricks.RepeatBrick; import org.catrobat.catroid.content.bricks.RepeatUntilBrick; import org.catrobat.catroid.content.bricks.ReplaceItemInUserListBrick; @@ -167,6 +170,7 @@ import org.catrobat.catroid.content.bricks.StopAllSoundsBrick; import org.catrobat.catroid.content.bricks.StopRunningStitchBrick; import org.catrobat.catroid.content.bricks.StopScriptBrick; +import org.catrobat.catroid.content.bricks.StopSoundBrick; import org.catrobat.catroid.content.bricks.StoreCSVIntoUserListBrick; import org.catrobat.catroid.content.bricks.TapAtBrick; import org.catrobat.catroid.content.bricks.ThinkBubbleBrick; @@ -192,6 +196,7 @@ import org.catrobat.catroid.content.bricks.WhenTouchDownBrick; import org.catrobat.catroid.content.bricks.WriteListOnDeviceBrick; import org.catrobat.catroid.content.bricks.WriteVariableOnDeviceBrick; +import org.catrobat.catroid.content.bricks.WriteVariableToFileBrick; import org.catrobat.catroid.content.bricks.ZigZagStitchBrick; import org.catrobat.catroid.ui.fragment.CategoryBricksFactory; import org.junit.Before; @@ -294,6 +299,7 @@ public static Collection data() { ClearBackgroundBrick.class)}, {"Sound", Arrays.asList(PlaySoundBrick.class, PlaySoundAndWaitBrick.class, + StopSoundBrick.class, StopAllSoundsBrick.class, SetVolumeToBrick.class, ChangeVolumeByNBrick.class, @@ -308,6 +314,7 @@ public static Collection data() { WaitUntilBrick.class, RepeatBrick.class, RepeatUntilBrick.class, + ForVariableFromToBrick.class, SceneTransitionBrick.class, SceneStartBrick.class, StopScriptBrick.class, @@ -321,6 +328,8 @@ public static Collection data() { HideTextBrick.class, WriteVariableOnDeviceBrick.class, ReadVariableFromDeviceBrick.class, + WriteVariableToFileBrick.class, + ReadVariableFromFileBrick.class, AddItemToUserListBrick.class, DeleteItemOfUserListBrick.class, ClearUserListBrick.class, @@ -386,6 +395,7 @@ public static Collection data() { RaspiSendDigitalValueBrick.class, RaspiPwmBrick.class)}, {"Testing", Arrays.asList(AssertEqualsBrick.class, + AssertUserListsBrick.class, WaitTillIdleBrick.class, TapAtBrick.class, FinishStageBrick.class, diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/test/io/SoundManagerTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/test/io/SoundManagerTest.java index d68411976a2..33b30f75474 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/test/io/SoundManagerTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/test/io/SoundManagerTest.java @@ -25,9 +25,9 @@ import android.media.MediaPlayer; import org.catrobat.catroid.ProjectManager; +import org.catrobat.catroid.content.MediaPlayerWithSoundDetails; import org.catrobat.catroid.content.Project; import org.catrobat.catroid.content.Sprite; -import org.catrobat.catroid.io.ResourceImporter; import org.catrobat.catroid.io.SoundManager; import org.catrobat.catroid.io.XstreamSerializer; import org.catrobat.catroid.test.R; @@ -46,13 +46,11 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.platform.app.InstrumentationRegistry; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; -import static org.catrobat.catroid.common.Constants.SOUND_DIRECTORY_NAME; import static org.mockito.Mockito.verify; @RunWith(AndroidJUnit4.class) @@ -62,20 +60,18 @@ public class SoundManagerTest { public final ExpectedException exception = ExpectedException.none(); private final SoundManager soundManager = SoundManager.getInstance(); - + private static final int NUMBER_OF_SOUNDFILES = 3; private Project project; - private File soundFile; + private final File[] soundFiles = new File[NUMBER_OF_SOUNDFILES]; @Before public void setUp() throws Exception { TestUtils.deleteProjects(); createProject(); soundManager.clear(); - - soundFile = ResourceImporter - .createSoundFileFromResourcesInDirectory(InstrumentationRegistry.getInstrumentation().getContext().getResources(), - R.raw.testsound, new File(project.getDefaultScene().getDirectory(), SOUND_DIRECTORY_NAME), - "testsound.m4a"); + soundFiles[0] = TestUtils.createSoundFile(project, R.raw.testsound, "testsound.m4a"); + soundFiles[1] = TestUtils.createSoundFile(project, R.raw.testsoundui, "testsoundui.mp3"); + soundFiles[2] = TestUtils.createSoundFile(project, R.raw.longsound, "longsound.mp3"); } @After @@ -86,7 +82,8 @@ public void tearDown() throws Exception { @Test public void testPlaySound() { - soundManager.playSoundFile(soundFile.getAbsolutePath()); + soundManager.playSoundFile(soundFiles[0].getAbsolutePath(), + project.getDefaultScene().getBackgroundSprite()); MediaPlayer mediaPlayer = soundManager.getMediaPlayers().get(0); assertTrue(mediaPlayer.isPlaying()); @@ -95,7 +92,8 @@ public void testPlaySound() { @Test public void testClear() { - soundManager.playSoundFile(soundFile.getAbsolutePath()); + soundManager.playSoundFile(soundFiles[1].getAbsolutePath(), + project.getDefaultScene().getBackgroundSprite()); MediaPlayer mediaPlayer = soundManager.getMediaPlayers().get(0); assertTrue(mediaPlayer.isPlaying()); @@ -109,7 +107,8 @@ public void testClear() { @Test public void testPauseAndResume() { - soundManager.playSoundFile(soundFile.getAbsolutePath()); + soundManager.playSoundFile(soundFiles[0].getAbsolutePath(), + project.getDefaultScene().getBackgroundSprite()); MediaPlayer mediaPlayer = soundManager.getMediaPlayers().get(0); assertTrue(mediaPlayer.isPlaying()); @@ -123,49 +122,82 @@ public void testPauseAndResume() { @Test public void testPauseAndResumeMultipleSounds() { - final int playSoundFilesCount = 3; - List mediaPlayers = soundManager.getMediaPlayers(); + List mediaPlayers = soundManager.getMediaPlayers(); - for (int index = 0; index < playSoundFilesCount; index++) { - soundManager.playSoundFile(soundFile.getAbsolutePath()); + for (int index = 0; index < NUMBER_OF_SOUNDFILES; index++) { + soundManager.playSoundFile(soundFiles[index].getAbsolutePath(), + Mockito.mock(Sprite.class)); } - for (int index = 0; index < playSoundFilesCount; index++) { + for (int index = 0; index < NUMBER_OF_SOUNDFILES; index++) { assertTrue(mediaPlayers.get(index).isPlaying()); } soundManager.pause(); - for (int index = 0; index < playSoundFilesCount; index++) { + for (int index = 0; index < NUMBER_OF_SOUNDFILES; index++) { assertFalse(mediaPlayers.get(index).isPlaying()); } soundManager.resume(); - for (int index = 0; index < playSoundFilesCount; index++) { + for (int index = 0; index < NUMBER_OF_SOUNDFILES; index++) { assertTrue(mediaPlayers.get(index).isPlaying()); } } + @Test + public void testStopOfSoundWhenSameSoundIsStarted() { + List mediaPlayers = soundManager.getMediaPlayers(); + + for (int index = 0; index < NUMBER_OF_SOUNDFILES; index++) { + soundManager.playSoundFile(soundFiles[0].getAbsolutePath(), + project.getDefaultScene().getBackgroundSprite()); + } + assertEquals(1, mediaPlayers.size()); + assertTrue(mediaPlayers.get(0).isPlaying()); + } + + @Test + public void testPlaySameSoundDifferentSprite() { + List mediaPlayers = soundManager.getMediaPlayers(); + soundManager.playSoundFile(soundFiles[2].getAbsolutePath(), + project.getDefaultScene().getBackgroundSprite()); + soundManager.playSoundFile(soundFiles[2].getAbsolutePath(), + project.getDefaultScene().getSpriteList().get(1)); + assertTrue(mediaPlayers.get(0).isPlaying()); + assertTrue(mediaPlayers.get(1).isPlaying()); + } + + @Test + public void testPlaySameSoundFirstStopped() { + soundManager.playSoundFile(soundFiles[0].getAbsolutePath(), + project.getDefaultScene().getBackgroundSprite()); + soundManager.getMediaPlayers().get(0).stop(); + assertFalse(soundManager.getMediaPlayers().get(0).isPlaying()); + soundManager.playSoundFile(soundFiles[0].getAbsolutePath(), + project.getDefaultScene().getBackgroundSprite()); + assertTrue(soundManager.getMediaPlayers().get(0).isPlaying()); + } + @Test public void testMediaPlayerLimit() { assertEquals(7, SoundManager.MAX_MEDIA_PLAYERS); - List mediaPlayers = soundManager.getMediaPlayers(); for (int index = 0; index < SoundManager.MAX_MEDIA_PLAYERS + 3; index++) { - soundManager.playSoundFile(soundFile.getAbsolutePath()); + soundManager.playSoundFile(soundFiles[0].getAbsolutePath(), Mockito.mock(Sprite.class)); } - assertEquals(SoundManager.MAX_MEDIA_PLAYERS, mediaPlayers.size()); + assertEquals(SoundManager.MAX_MEDIA_PLAYERS, soundManager.getMediaPlayers().size()); } @Test public void testIfAllMediaPlayersInTheListAreUnique() { - List mediaPlayers = soundManager.getMediaPlayers(); for (int index = 0; index < SoundManager.MAX_MEDIA_PLAYERS; index++) { - SoundManager.getInstance().playSoundFile(soundFile.getAbsolutePath()); + SoundManager.getInstance().playSoundFile(soundFiles[0].getAbsolutePath(), + Mockito.mock(Sprite.class)); } - + List mediaPlayers = soundManager.getMediaPlayers(); for (MediaPlayer mediaPlayer : mediaPlayers) { assertEquals(1, Collections.frequency(mediaPlayers, mediaPlayer)); } @@ -179,9 +211,9 @@ public void testInitialVolume() { @Test public void testSetVolume() { - List mediaPlayers = soundManager.getMediaPlayers(); - MediaPlayer mediaPlayerMock = Mockito.mock(MediaPlayer.class); - mediaPlayers.add(mediaPlayerMock); + MediaPlayerWithSoundDetails mediaPlayerMock = Mockito.mock( + MediaPlayerWithSoundDetails.class); + soundManager.getMediaPlayers().add(mediaPlayerMock); float newVolume = 80.9f; soundManager.setVolume(newVolume); diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/test/physics/collision/CollisionDetectionAdvancedTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/test/physics/collision/CollisionDetectionAdvancedTest.java index 9a51d01f689..a114efafe33 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/test/physics/collision/CollisionDetectionAdvancedTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/test/physics/collision/CollisionDetectionAdvancedTest.java @@ -26,8 +26,6 @@ import com.badlogic.gdx.math.Polygon; import com.badlogic.gdx.scenes.scene2d.Action; -import junit.framework.Assert; - import org.catrobat.catroid.ProjectManager; import org.catrobat.catroid.common.LookData; import org.catrobat.catroid.content.ActionFactory; @@ -52,13 +50,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; -import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; import static org.catrobat.catroid.common.Constants.IMAGE_DIRECTORY_NAME; import static org.catrobat.catroid.test.physics.PhysicsTestUtils.generateLookData; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; @RunWith(AndroidJUnit4.class) public class CollisionDetectionAdvancedTest { @@ -111,18 +109,18 @@ public void setUp() throws Exception { Polygon[] collisionPolygons1 = sprite1.look.getLookData().getCollisionInformation().collisionPolygons; Polygon[] collisionPolygons2 = sprite2.look.getLookData().getCollisionInformation().collisionPolygons; - Assert.assertNotNull(collisionPolygons1); - Assert.assertEquals(2, collisionPolygons1.length); + assertNotNull(collisionPolygons1); + assertEquals(2, collisionPolygons1.length); - Assert.assertNotNull(collisionPolygons2); - Assert.assertEquals(3, collisionPolygons2.length); + assertNotNull(collisionPolygons2); + assertEquals(3, collisionPolygons2.length); XstreamSerializer.getInstance().saveProject(project); } @Test public void testCollisionBetweenMovingLooks() { - assertEquals(0d, CollisionDetection.checkCollisionBetweenLooks(sprite1.look, sprite2.look)); + assertFalse(CollisionDetection.checkCollisionBetweenLooks(sprite1.look, sprite2.look)); float steps = 200.0f; ActionFactory factory = new ActionFactory(); @@ -130,12 +128,12 @@ public void testCollisionBetweenMovingLooks() { Action moveNSteptsaction = factory.createMoveNStepsAction(sprite2, new Formula(steps)); moveNSteptsaction.act(1.0f); - assertThat(CollisionDetection.checkCollisionBetweenLooks(sprite1.look, sprite2.look), is(greaterThan(0d))); + assertTrue(CollisionDetection.checkCollisionBetweenLooks(sprite1.look, sprite2.look)); } @Test public void testCollisionBetweenExpandingLooks() { - assertEquals(0d, CollisionDetection.checkCollisionBetweenLooks(sprite1.look, sprite2.look)); + assertFalse(CollisionDetection.checkCollisionBetweenLooks(sprite1.look, sprite2.look)); float size = 300.0f; ActionFactory factory = new ActionFactory(); @@ -143,6 +141,6 @@ public void testCollisionBetweenExpandingLooks() { Action createChangeSizeByNAction = factory.createChangeSizeByNAction(sprite2, new Formula(size)); createChangeSizeByNAction.act(1.0f); - assertThat(CollisionDetection.checkCollisionBetweenLooks(sprite1.look, sprite2.look), is(greaterThan(0d))); + assertTrue(CollisionDetection.checkCollisionBetweenLooks(sprite1.look, sprite2.look)); } } diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/test/sensing/TouchesEdgeTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/test/sensing/TouchesEdgeTest.java index 5264f2487fd..e8b2102b15b 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/test/sensing/TouchesEdgeTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/test/sensing/TouchesEdgeTest.java @@ -23,6 +23,8 @@ package org.catrobat.catroid.test.sensing; +import com.badlogic.gdx.math.Rectangle; + import org.catrobat.catroid.ProjectManager; import org.catrobat.catroid.content.Project; import org.catrobat.catroid.content.Sprite; @@ -38,17 +40,16 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; -import static junit.framework.Assert.assertEquals; - -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; @RunWith(AndroidJUnit4.class) public class TouchesEdgeTest { protected Project project; protected Sprite sprite1; + private int virtualScreenWidth; + private int virtualScreenHeight; + private Rectangle screen; @Before public void setUp() throws Exception { @@ -63,49 +64,44 @@ public void setUp() throws Exception { CollisionTestUtils.initializeSprite(sprite1, org.catrobat.catroid.test.R.raw.collision_donut, "collision_donut.png", InstrumentationRegistry.getInstrumentation().getContext(), project); + virtualScreenWidth = project.getXmlHeader().virtualScreenWidth; + virtualScreenHeight = project.getXmlHeader().virtualScreenHeight; + screen = project.getScreenRectangle(); + + sprite1.look.setXInUserInterfaceDimensionUnit(0); + sprite1.look.setYInUserInterfaceDimensionUnit(0); + } + + @Test + public void testNoCollisionInCenter() { + assertFalse(CollisionDetection.collidesWithEdge(sprite1.look.getCurrentCollisionPolygon(), screen)); } @Test public void testCollisionWithRightEdge() { - sprite1.look.setXInUserInterfaceDimensionUnit(0); - sprite1.look.setYInUserInterfaceDimensionUnit(0); - assertThat(CollisionDetection.collidesWithEdge(sprite1.look), is(not(equalTo(1d)))); - int virtualScreenWidth = ProjectManager.getInstance().getCurrentProject().getXmlHeader().virtualScreenWidth; sprite1.look.setXInUserInterfaceDimensionUnit(sprite1.look.getXInUserInterfaceDimensionUnit() - + virtualScreenWidth / 2); - assertEquals(1d, CollisionDetection.collidesWithEdge(sprite1.look)); + + virtualScreenWidth / 2f); + assertTrue(CollisionDetection.collidesWithEdge(sprite1.look.getCurrentCollisionPolygon(), screen)); } @Test public void testCollisionWithLeftEdge() { - sprite1.look.setXInUserInterfaceDimensionUnit(0); - sprite1.look.setYInUserInterfaceDimensionUnit(0); - assertThat(CollisionDetection.collidesWithEdge(sprite1.look), is(not(equalTo(1d)))); - int virtualScreenWidth = ProjectManager.getInstance().getCurrentProject().getXmlHeader().virtualScreenWidth; sprite1.look.setXInUserInterfaceDimensionUnit(sprite1.look.getXInUserInterfaceDimensionUnit() - - virtualScreenWidth / 2); - assertEquals(1d, CollisionDetection.collidesWithEdge(sprite1.look)); + - virtualScreenWidth / 2f); + assertTrue(CollisionDetection.collidesWithEdge(sprite1.look.getCurrentCollisionPolygon(), screen)); } @Test public void testCollisionWithUpperEdge() { - sprite1.look.setXInUserInterfaceDimensionUnit(0); - sprite1.look.setYInUserInterfaceDimensionUnit(0); - assertThat(CollisionDetection.collidesWithEdge(sprite1.look), is(not(equalTo(1d)))); - int virtualScreenHeight = ProjectManager.getInstance().getCurrentProject().getXmlHeader().virtualScreenHeight; sprite1.look.setYInUserInterfaceDimensionUnit(sprite1.look.getXInUserInterfaceDimensionUnit() - + virtualScreenHeight / 2); - assertEquals(1d, CollisionDetection.collidesWithEdge(sprite1.look)); + + virtualScreenHeight / 2f); + assertTrue(CollisionDetection.collidesWithEdge(sprite1.look.getCurrentCollisionPolygon(), screen)); } @Test public void testCollisionWithBottomEdge() { - sprite1.look.setXInUserInterfaceDimensionUnit(0); - sprite1.look.setYInUserInterfaceDimensionUnit(0); - assertThat(CollisionDetection.collidesWithEdge(sprite1.look), is(not(equalTo(1d)))); - int virtualScreenHeight = ProjectManager.getInstance().getCurrentProject().getXmlHeader().virtualScreenHeight; sprite1.look.setYInUserInterfaceDimensionUnit(sprite1.look.getXInUserInterfaceDimensionUnit() - - virtualScreenHeight / 2); - assertEquals(1d, CollisionDetection.collidesWithEdge(sprite1.look)); + - virtualScreenHeight / 2f); + assertTrue(CollisionDetection.collidesWithEdge(sprite1.look.getCurrentCollisionPolygon(), screen)); } } diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/test/sensing/TouchesFingerTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/test/sensing/TouchesFingerTest.java index 9da4d031047..3c2066513e7 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/test/sensing/TouchesFingerTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/test/sensing/TouchesFingerTest.java @@ -70,10 +70,12 @@ public void setUp() throws Exception { public void testBasicOneTouchingPoint() { TouchUtil.reset(); TouchUtil.touchDown(150, 150, 1); - assertEquals(1d, CollisionDetection.collidesWithFinger(sprite1.look)); + assertEquals(1d, CollisionDetection.collidesWithFinger( + sprite1.look.getCurrentCollisionPolygon(), TouchUtil.getCurrentTouchingPoints())); TouchUtil.touchUp(1); TouchUtil.touchDown(0, 0, 1); - assertThat(CollisionDetection.collidesWithFinger(sprite1.look), is(not(equalTo(1d)))); + assertThat(CollisionDetection.collidesWithFinger( + sprite1.look.getCurrentCollisionPolygon(), TouchUtil.getCurrentTouchingPoints()), is(not(equalTo(1d)))); } @Test @@ -82,7 +84,8 @@ public void testBasicMultipleTouchingPoints() { TouchUtil.touchDown(150, 150, 1); TouchUtil.touchDown(0, 0, 2); TouchUtil.touchDown(151, 151, 3); - assertEquals(1d, CollisionDetection.collidesWithFinger(sprite1.look)); + assertEquals(1d, CollisionDetection.collidesWithFinger( + sprite1.look.getCurrentCollisionPolygon(), TouchUtil.getCurrentTouchingPoints())); } @Test @@ -90,7 +93,8 @@ public void testAdvancedOneTouchingPoint() { TouchUtil.reset(); TouchUtil.touchDown(0, 0, 1); - assertThat(CollisionDetection.collidesWithFinger(sprite1.look), is(not(equalTo(1d)))); + assertThat(CollisionDetection.collidesWithFinger( + sprite1.look.getCurrentCollisionPolygon(), TouchUtil.getCurrentTouchingPoints()), is(not(equalTo(1d)))); float x = sprite1.look.getXInUserInterfaceDimensionUnit(); float y = sprite1.look.getYInUserInterfaceDimensionUnit(); @@ -98,6 +102,7 @@ public void testAdvancedOneTouchingPoint() { sprite1.look.setXInUserInterfaceDimensionUnit(x - 150); sprite1.look.setYInUserInterfaceDimensionUnit(y - 150); - assertEquals(1d, CollisionDetection.collidesWithFinger(sprite1.look)); + assertEquals(1d, CollisionDetection.collidesWithFinger( + sprite1.look.getCurrentCollisionPolygon(), TouchUtil.getCurrentTouchingPoints())); } } diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/test/ui/AddBrickFloatingBehaviorTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/test/ui/AddBrickFloatingBehaviorTest.java new file mode 100644 index 00000000000..25a19bea726 --- /dev/null +++ b/catroid/src/androidTest/java/org/catrobat/catroid/test/ui/AddBrickFloatingBehaviorTest.java @@ -0,0 +1,103 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2020 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.test.ui; + +import org.catrobat.catroid.content.Sprite; +import org.catrobat.catroid.content.StartScript; +import org.catrobat.catroid.content.bricks.Brick; +import org.catrobat.catroid.content.bricks.GlideToBrick; +import org.catrobat.catroid.content.bricks.ScriptBrick; +import org.catrobat.catroid.content.bricks.WhenStartedBrick; +import org.catrobat.catroid.ui.dragndrop.BrickListView; +import org.catrobat.catroid.ui.recyclerview.adapter.BrickAdapter; +import org.catrobat.catroid.ui.recyclerview.fragment.ScriptFragment; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; + +@RunWith(Parameterized.class) +public class AddBrickFloatingBehaviorTest { + + @Parameterized.Parameters(name = "{0}") + public static Collection data() { + return Arrays.asList(new Object[][] { + {"FirstAddedIsScriptBrick", 0, scriptBrick, 0}, + {"FirstAddedIsBrick", 0, brick, 0}, + {"SecondAddedIsScriptBrick", 1, scriptBrick, 1}, + {"SecondAddedIsBrick", 1, brick, 0}, + {"ThirdAddedIsScriptBrick", 2, scriptBrick, 1}, + {"ThirdAddedIsBrick", 2, brick, 1}, + }); + } + + @Parameterized.Parameter + public String name; + + @Parameterized.Parameter(1) + public int alreadyAddedBricksCount; + + @Parameterized.Parameter(2) + public Brick brickToAdd; + + @Parameterized.Parameter(3) + public int expectedFloating; + + @Spy + private ScriptFragment scriptFragmentMock; + @Mock + private Sprite spriteMock; + @Mock + private BrickAdapter brickAdapterMock; + @Mock + private BrickListView brickListViewMock; + + private static Brick brick = new GlideToBrick(); + private static ScriptBrick scriptBrick = new WhenStartedBrick(); + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + Mockito.when(brickAdapterMock.getCount()).thenReturn(alreadyAddedBricksCount); + Mockito.doNothing().when(brickAdapterMock).addItem(anyInt(), any(Brick.class)); + Mockito.when(spriteMock.getScriptList()).thenReturn(Collections.singletonList(new StartScript())); + } + + @Test + public void testAddBrickFloatingBehaviour() { + scriptFragmentMock.addBrick(brickToAdd, spriteMock, brickAdapterMock, brickListViewMock); + Mockito.verify(brickListViewMock, Mockito.times(expectedFloating)).startMoving(brickToAdd); + } +} diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/test/utils/TestUtils.java b/catroid/src/androidTest/java/org/catrobat/catroid/test/utils/TestUtils.java index 47184d13f6b..fc35c6d7870 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/test/utils/TestUtils.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/test/utils/TestUtils.java @@ -37,6 +37,7 @@ import org.catrobat.catroid.content.StartScript; import org.catrobat.catroid.content.bricks.Brick; import org.catrobat.catroid.content.bricks.HideBrick; +import org.catrobat.catroid.io.ResourceImporter; import org.catrobat.catroid.io.StorageOperations; import org.catrobat.catroid.io.XstreamSerializer; @@ -44,10 +45,14 @@ import java.io.IOException; import androidx.test.core.app.ApplicationProvider; +import androidx.test.platform.app.InstrumentationRegistry; + +import static org.catrobat.catroid.common.Constants.SOUND_DIRECTORY_NAME; public final class TestUtils { public static final String DEFAULT_TEST_PROJECT_NAME = "testProject"; + public static final String DEFAULT_TEST_SPRITE_NAME = "testProject"; public static final double DELTA = 0.00001; @@ -93,4 +98,11 @@ public static Pixmap createRectanglePixmap(int width, int height, Color color) { pixmap.fillRectangle(0, 0, width, height); return pixmap; } + + public static File createSoundFile(Project project, int source, String soundName) throws IOException { + return ResourceImporter + .createSoundFileFromResourcesInDirectory(InstrumentationRegistry.getInstrumentation().getContext().getResources(), + source, new File(project.getDefaultScene().getDirectory(), + SOUND_DIRECTORY_NAME), soundName); + } } diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/content/brick/rtl/RtlBrickTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/content/brick/rtl/RtlBrickTest.java index 5abd31f5cb3..59340fbb441 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/content/brick/rtl/RtlBrickTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/content/brick/rtl/RtlBrickTest.java @@ -194,10 +194,10 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; -import static org.catrobat.catroid.ui.settingsfragments.SettingsFragment.PARROT_JUMPING_SUMO_SCREEN_KEY; import static org.catrobat.catroid.ui.settingsfragments.SettingsFragment.SETTINGS_MINDSTORMS_EV3_BRICKS_ENABLED; import static org.catrobat.catroid.ui.settingsfragments.SettingsFragment.SETTINGS_MINDSTORMS_NXT_BRICKS_ENABLED; import static org.catrobat.catroid.ui.settingsfragments.SettingsFragment.SETTINGS_SHOW_ARDUINO_BRICKS; +import static org.catrobat.catroid.ui.settingsfragments.SettingsFragment.SETTINGS_SHOW_JUMPING_SUMO_BRICKS; import static org.catrobat.catroid.ui.settingsfragments.SettingsFragment.SETTINGS_SHOW_NFC_BRICKS; import static org.catrobat.catroid.ui.settingsfragments.SettingsFragment.SETTINGS_SHOW_PARROT_AR_DRONE_BRICKS; import static org.catrobat.catroid.ui.settingsfragments.SettingsFragment.SETTINGS_SHOW_PHIRO_BRICKS; @@ -228,7 +228,7 @@ public class RtlBrickTest { private List allPeripheralCategories = new ArrayList<>(Arrays.asList(SETTINGS_MINDSTORMS_NXT_BRICKS_ENABLED, SETTINGS_MINDSTORMS_EV3_BRICKS_ENABLED, SETTINGS_SHOW_PARROT_AR_DRONE_BRICKS, SETTINGS_SHOW_PHIRO_BRICKS, SETTINGS_SHOW_ARDUINO_BRICKS, SETTINGS_SHOW_RASPI_BRICKS, SETTINGS_SHOW_NFC_BRICKS, - PARROT_JUMPING_SUMO_SCREEN_KEY)); + SETTINGS_SHOW_JUMPING_SUMO_BRICKS)); private List enabledByThisTestPeripheralCategories = new ArrayList<>(); @Before @@ -650,7 +650,7 @@ public void userBricks() { assertEquals(arLocale.getDisplayLanguage(), Locale.getDefault().getDisplayLanguage()); assertTrue(RtlUiTestUtils.checkTextDirectionIsRtl(Locale.getDefault().getDisplayName())); openCategory(R.string.category_user_bricks); - onView(allOf(withId(android.R.id.list), withParent(withId(R.id.add_brick_fragment_list)))).check(matches(hasChildCount(0))); + onView(allOf(withId(android.R.id.list), withParent(withId(R.id.fragment_user_defined_brick_list)))).check(matches(hasChildCount(0))); } @Category({Cat.AppUi.class, Level.Smoke.class, Cat.RTLTests.class}) diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/content/brick/stage/PlaySoundAndWaitStageTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/content/brick/stage/PlaySoundAndWaitStageTest.java new file mode 100644 index 00000000000..2d76273fb95 --- /dev/null +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/content/brick/stage/PlaySoundAndWaitStageTest.java @@ -0,0 +1,135 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2020 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.content.brick.stage; + +import org.catrobat.catroid.ProjectManager; +import org.catrobat.catroid.common.SoundInfo; +import org.catrobat.catroid.content.MediaPlayerWithSoundDetails; +import org.catrobat.catroid.content.Project; +import org.catrobat.catroid.content.Script; +import org.catrobat.catroid.content.Sprite; +import org.catrobat.catroid.content.StartScript; +import org.catrobat.catroid.content.bricks.Brick; +import org.catrobat.catroid.content.bricks.PlaySoundAndWaitBrick; +import org.catrobat.catroid.content.bricks.RepeatBrick; +import org.catrobat.catroid.content.bricks.SetVariableBrick; +import org.catrobat.catroid.content.bricks.WaitBrick; +import org.catrobat.catroid.formulaeditor.Formula; +import org.catrobat.catroid.formulaeditor.UserVariable; +import org.catrobat.catroid.io.SoundManager; +import org.catrobat.catroid.io.XstreamSerializer; +import org.catrobat.catroid.stage.StageActivity; +import org.catrobat.catroid.test.utils.TestUtils; +import org.catrobat.catroid.uiespresso.stage.utils.ScriptEvaluationGateBrick; +import org.catrobat.catroid.uiespresso.util.rules.BaseActivityTestRule; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import static junit.framework.TestCase.assertEquals; + +@RunWith(AndroidJUnit4.class) +public class PlaySoundAndWaitStageTest { + + private static ProjectManager projectManager; + private SoundInfo soundInfo; + private ScriptEvaluationGateBrick scriptEvaluationGateBrick; + private Project project; + private UserVariable userVariable; + private RepeatBrick repeatBrick; + + @BeforeClass + public static void setUpProjectManager() { + projectManager = ProjectManager.getInstance(); + } + + @Rule + public BaseActivityTestRule baseActivityTestRule = new + BaseActivityTestRule<>(StageActivity.class, false, false); + + @Before + public void setUp() throws IOException { + createProject(getClass().getSimpleName()); + baseActivityTestRule.launchActivity(null); + } + + @Test + public void testPlayAndWaitInLoop() { + scriptEvaluationGateBrick.waitUntilEvaluated(5000); + List mediaPlayers = + SoundManager.getInstance().getMediaPlayers(); + assertEquals(mediaPlayers.get(0).getDuration(), mediaPlayers.get(0).getCurrentPosition(), 150); + } + + @Test + public void testPlayAndWaitStopSameSoundDifferentScript() { + repeatBrick.setFormulaWithBrickField(Brick.BrickField.TIMES_TO_REPEAT, new Formula(1)); + Script script2 = new StartScript(); + PlaySoundAndWaitBrick soundBrick = new PlaySoundAndWaitBrick(); + soundBrick.setSound(soundInfo); + script2.addBrick(soundBrick); + soundBrick.setParent(script2.getScriptBrick()); + projectManager.getCurrentSprite().getSoundList().add(soundInfo); + project.getDefaultScene().getBackgroundSprite().addScript(script2); + ScriptEvaluationGateBrick.appendToScript(script2).waitUntilEvaluated(5000); + assertEquals(1.0, userVariable.getValue()); + } + + private void createProject(String projectName) throws IOException { + project = new Project(ApplicationProvider.getApplicationContext(), projectName); + Sprite sprite = project.getDefaultScene().getBackgroundSprite(); + XstreamSerializer.getInstance().saveProject(project); + project.getDefaultScene().addSprite(sprite); + projectManager.setCurrentProject(project); + projectManager.setCurrentSprite(sprite); + Script script = new StartScript(); + PlaySoundAndWaitBrick soundBrick = new PlaySoundAndWaitBrick(); + File soundFile = TestUtils.createSoundFile(project, org.catrobat.catroid.test.R.raw.testsound2, + "testsound2.mp3"); + soundInfo = new SoundInfo(); + soundInfo.setFile(soundFile); + soundInfo.setName("testSound"); + soundBrick.setSound(soundInfo); + repeatBrick = new RepeatBrick(new Formula(2)); + repeatBrick.addBrick(soundBrick); + repeatBrick.setParent(script.getScriptBrick()); + userVariable = new UserVariable("variable", 0); + SetVariableBrick setVariableBrick = new SetVariableBrick(new Formula(1), userVariable); + script.addBrick(repeatBrick); + script.addBrick(new WaitBrick(100)); + script.addBrick(setVariableBrick); + project.getDefaultScene().getBackgroundSprite().addScript(script); + projectManager.getCurrentSprite().getSoundList().add(soundInfo); + scriptEvaluationGateBrick = ScriptEvaluationGateBrick.appendToScript(script); + } +} diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/actionbar/ActionBarTitleFullyDisplayedTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/actionbar/ActionBarTitleFullyDisplayedTest.java new file mode 100644 index 00000000000..a057621cf37 --- /dev/null +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/actionbar/ActionBarTitleFullyDisplayedTest.java @@ -0,0 +1,110 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2020 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.ui.actionbar; + +import android.widget.TextView; + +import org.catrobat.catroid.ProjectManager; +import org.catrobat.catroid.R; +import org.catrobat.catroid.content.Project; +import org.catrobat.catroid.content.Scene; +import org.catrobat.catroid.io.asynctask.ProjectSaveTask; +import org.catrobat.catroid.testsuites.annotations.Cat; +import org.catrobat.catroid.testsuites.annotations.Level; +import org.catrobat.catroid.ui.ProjectActivity; +import org.catrobat.catroid.uiespresso.util.UiTestUtils; +import org.catrobat.catroid.uiespresso.util.rules.BaseActivityTestRule; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import androidx.test.core.app.ApplicationProvider; + +import static org.catrobat.catroid.uiespresso.ui.actionbar.utils.ActionBarWrapper.onActionBar; +import static org.catrobat.catroid.uiespresso.ui.actionbar.utils.ActionModeWrapper.onActionMode; + +import static androidx.test.InstrumentationRegistry.getInstrumentation; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; + +public class ActionBarTitleFullyDisplayedTest { + @Rule + public BaseActivityTestRule baseActivityTestRule = new + BaseActivityTestRule<>(ProjectActivity.class, false, false); + + @Before + public void setUp() throws Exception { + createTestProject("ActionBarTitleFullyDisplayedTest"); + baseActivityTestRule.launchActivity(null); + } + + @Category({Cat.AppUi.class, Level.Smoke.class}) + @Test + public void actionBarTitleFullyDisplayedTest() { + String currentProjectName = ProjectManager.getInstance().getCurrentProject().getName(); + + onActionBar() + .checkTitleMatches(currentProjectName); + + openActionBarOverflowOrOptionsMenu(getInstrumentation().getTargetContext()); + onView(withText(R.string.delete)) + .perform(click()); + + assertIsTextCompletelyDisplayed(baseActivityTestRule); + + onView(withId(R.id.overflow)) + .perform(click()); + + onView(withText(R.string.select_all)) + .perform(click()); + + assertIsTextCompletelyDisplayed(baseActivityTestRule); + onActionMode().checkTitleMatches(UiTestUtils.getResourcesString(R.string.delete) + " 4"); + } + + private void createTestProject(String projectName) { + Project project = new Project(ApplicationProvider.getApplicationContext(), projectName); + Scene sceneOne = new Scene("testScene1", project); + Scene sceneTwo = new Scene("testScene2", project); + Scene sceneThree = new Scene("testScene2", project); + + project.addScene(sceneOne); + project.addScene(sceneTwo); + project.addScene(sceneThree); + + ProjectManager.getInstance().setCurrentProject(project); + ProjectSaveTask + .task(project, ApplicationProvider.getApplicationContext()); + } + + public void assertIsTextCompletelyDisplayed(BaseActivityTestRule activity) { + TextView text = activity.getActivity().findViewById(R.id.action_bar_title); + Assert.assertEquals(text.getLayout().getEllipsisCount(text.getLayout().getLineCount() - 1), 0); + } +} diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/actionbar/ActionModeDataFragmentTitleTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/actionbar/ActionModeDataFragmentTitleTest.java index 1ee4beec5b8..373ace6fd46 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/actionbar/ActionModeDataFragmentTitleTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/actionbar/ActionModeDataFragmentTitleTest.java @@ -70,6 +70,7 @@ public void setUp() throws Exception { @Test public void actionModeDataFragmentTitleTest() { openContextualActionModeOverflowMenu(); + onView(withText(R.string.delete)) .perform(click()); @@ -82,9 +83,7 @@ public void actionModeDataFragmentTitleTest() { onDataList().onListAtPosition(2) .performCheckItem(); - onActionMode() - .checkTitleMatches(String.format(UiTestUtils.getQuantitiyString(R.plurals - .am_delete_user_data_items_title, 2), 2)); + onActionMode().checkTitleMatches(UiTestUtils.getResourcesString(R.string.delete) + " 2"); } private void createProject() { diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/actionbar/ActionModeMergeTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/actionbar/ActionModeMergeTest.java index 3e50cd5e6ac..b5d2536843b 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/actionbar/ActionModeMergeTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/actionbar/ActionModeMergeTest.java @@ -117,27 +117,20 @@ public void actionModeMergeTitleTest() { onRecyclerView().atPosition(0) .performCheckItem(); - onActionMode() - .checkTitleMatches(String.format(UiTestUtils.getQuantitiyString(R.plurals - .am_merge_projects_title, 1), 1)); + onActionMode().checkTitleMatches(UiTestUtils.getResourcesString(R.string.merge) + " 1"); onRecyclerView().atPosition(1) .performCheckItem(); - onActionMode() - .checkTitleMatches(String.format(UiTestUtils.getQuantitiyString(R.plurals - .am_merge_projects_title, 2), 2)); + onActionMode().checkTitleMatches(UiTestUtils.getResourcesString(R.string.merge) + " 2"); onRecyclerView().atPosition(0) .performCheckItem(); - onActionMode() - .checkTitleMatches(String.format(UiTestUtils.getQuantitiyString(R.plurals - .am_merge_projects_title, 1), 1)); + onActionMode().checkTitleMatches(UiTestUtils.getResourcesString(R.string.merge) + " 1"); onRecyclerView().atPosition(1) .performCheckItem(); - onActionMode() - .checkTitleMatches(String.format(UiTestUtils.getQuantitiyString(R.plurals - .am_merge_projects_title, 0), 0)); + + onActionMode().checkTitleMatches(UiTestUtils.getResourcesString(R.string.merge) + " 0"); } private void createProject(String projectName) { diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/activity/PrivacyPolicyDisclaimerTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/activity/PrivacyPolicyDisclaimerTest.java index aa27a7fd8d9..c4c04c255bf 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/activity/PrivacyPolicyDisclaimerTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/activity/PrivacyPolicyDisclaimerTest.java @@ -100,8 +100,6 @@ public void testHidePrivacyPolicyDisclaimer() { onView(withText(R.string.main_menu_continue)) .check(matches(isDisplayed())); - onView(withText(R.string.main_menu_new)) - .check(matches(isDisplayed())); onView(withText(R.string.main_menu_programs)) .check(matches(isDisplayed())); } diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/activity/SettingsFragmentTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/activity/SettingsFragmentTest.java index fb0fedadf17..aa465cfd1bc 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/activity/SettingsFragmentTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/activity/SettingsFragmentTest.java @@ -23,14 +23,20 @@ package org.catrobat.catroid.uiespresso.ui.activity; +import android.app.Activity; +import android.app.Instrumentation; +import android.content.Intent; import android.content.SharedPreferences; +import android.net.Uri; import android.preference.PreferenceManager; import org.catrobat.catroid.R; +import org.catrobat.catroid.common.Constants; import org.catrobat.catroid.testsuites.annotations.Cat; import org.catrobat.catroid.testsuites.annotations.Level; import org.catrobat.catroid.ui.SettingsActivity; import org.catrobat.catroid.uiespresso.util.rules.BaseActivityTestRule; +import org.hamcrest.Matcher; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -46,6 +52,8 @@ import java.util.Map; import androidx.test.core.app.ApplicationProvider; +import androidx.test.espresso.action.ViewActions; +import androidx.test.espresso.intent.Intents; import androidx.test.espresso.matcher.PreferenceMatchers; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -54,7 +62,6 @@ import static org.catrobat.catroid.common.SharedPreferenceKeys.ACCESSIBILITY_PROFILE_PREFERENCE_KEY; import static org.catrobat.catroid.common.SharedPreferenceKeys.LANGUAGE_CODE; -import static org.catrobat.catroid.ui.settingsfragments.SettingsFragment.PARROT_JUMPING_SUMO_SCREEN_KEY; import static org.catrobat.catroid.ui.settingsfragments.SettingsFragment.SETTINGS_CAST_GLOBALLY_ENABLED; import static org.catrobat.catroid.ui.settingsfragments.SettingsFragment.SETTINGS_CRASH_REPORTS; import static org.catrobat.catroid.ui.settingsfragments.SettingsFragment.SETTINGS_MINDSTORMS_EV3_BRICKS_ENABLED; @@ -64,6 +71,7 @@ import static org.catrobat.catroid.ui.settingsfragments.SettingsFragment.SETTINGS_MULTIPLAYER_VARIABLES_ENABLED; import static org.catrobat.catroid.ui.settingsfragments.SettingsFragment.SETTINGS_SHOW_ARDUINO_BRICKS; import static org.catrobat.catroid.ui.settingsfragments.SettingsFragment.SETTINGS_SHOW_HINTS; +import static org.catrobat.catroid.ui.settingsfragments.SettingsFragment.SETTINGS_SHOW_JUMPING_SUMO_BRICKS; import static org.catrobat.catroid.ui.settingsfragments.SettingsFragment.SETTINGS_SHOW_NFC_BRICKS; import static org.catrobat.catroid.ui.settingsfragments.SettingsFragment.SETTINGS_SHOW_PARROT_AR_DRONE_BRICKS; import static org.catrobat.catroid.ui.settingsfragments.SettingsFragment.SETTINGS_SHOW_PHIRO_BRICKS; @@ -76,7 +84,12 @@ import static androidx.test.espresso.Espresso.onData; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.action.ViewActions.typeText; import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.intent.Intents.intended; +import static androidx.test.espresso.intent.Intents.intending; +import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction; +import static androidx.test.espresso.intent.matcher.IntentMatchers.hasData; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withParent; @@ -93,10 +106,11 @@ public class SettingsFragmentTest { SETTINGS_SHOW_PHIRO_BRICKS, SETTINGS_SHOW_NFC_BRICKS, SETTINGS_SHOW_HINTS, SETTINGS_CRASH_REPORTS, SETTINGS_MINDSTORMS_NXT_BRICKS_ENABLED, SETTINGS_MINDSTORMS_NXT_SHOW_SENSOR_INFO_BOX_DISABLED, SETTINGS_MINDSTORMS_EV3_BRICKS_ENABLED, SETTINGS_MINDSTORMS_EV3_SHOW_SENSOR_INFO_BOX_DISABLED, - SETTINGS_SHOW_PARROT_AR_DRONE_BRICKS, PARROT_JUMPING_SUMO_SCREEN_KEY, + SETTINGS_SHOW_PARROT_AR_DRONE_BRICKS, SETTINGS_SHOW_JUMPING_SUMO_BRICKS, SETTINGS_SHOW_RASPI_BRICKS, SETTINGS_MULTIPLAYER_VARIABLES_ENABLED, SETTINGS_CAST_GLOBALLY_ENABLED)); private Map initialSettings = new HashMap<>(); + private Matcher expectedBrowserIntent; @Before public void setUp() throws Exception { @@ -108,6 +122,14 @@ public void setUp() throws Exception { } setAllSettingsTo(true); baseActivityTestRule.launchActivity(null); + + Intents.init(); + expectedBrowserIntent = allOf( + hasAction(Intent.ACTION_VIEW), + hasData(Uri.parse(Constants.WEB_REQUEST_WIKI_URL))); + + intending(expectedBrowserIntent).respondWith( + new Instrumentation.ActivityResult(Activity.RESULT_OK, null)); } private void setAllSettingsTo(boolean value) { @@ -131,6 +153,7 @@ public void tearDown() { sharedPreferencesEditor.putInt(ACCESSIBILITY_PROFILE_PREFERENCE_KEY, R.id.default_profile); sharedPreferencesEditor.commit(); initialSettings.clear(); + Intents.release(); } @Category({Cat.AppUi.class, Level.Smoke.class}) @@ -138,6 +161,7 @@ public void tearDown() { public void basicSettingsTest() { checkPreference(R.string.preference_title_enable_arduino_bricks, SETTINGS_SHOW_ARDUINO_BRICKS); checkPreference(R.string.preference_title_enable_phiro_bricks, SETTINGS_SHOW_PHIRO_BRICKS); + checkPreference(R.string.preference_title_enable_jumpingsumo_bricks, SETTINGS_SHOW_JUMPING_SUMO_BRICKS); checkPreference(R.string.preference_title_enable_nfc_bricks, SETTINGS_SHOW_NFC_BRICKS); checkPreference(R.string.preference_title_enable_hints, SETTINGS_SHOW_HINTS); checkPreference(R.string.preference_title_enable_crash_reports, SETTINGS_CRASH_REPORTS); @@ -189,13 +213,6 @@ public void parrotArSettingsTest() { } @Category({Cat.AppUi.class, Level.Smoke.class, Cat.Gadgets.class}) - @Test - public void parrotJumpingSumoSettingsTest() { - onData(PreferenceMatchers.withTitle(R.string.preference_title_enable_jumpingsumo_bricks)).perform(click()); - - checkPreference(R.string.preference_title_enable_jumpingsumo_bricks, PARROT_JUMPING_SUMO_SCREEN_KEY); - } - @Test public void rasPiSettingsTest() { onData(PreferenceMatchers.withTitle(R.string.preference_title_enable_raspi_bricks)) @@ -234,6 +251,24 @@ public void languageSettingTest() { .check(matches(isDisplayed())); } + @Category({Cat.AppUi.class, Level.Smoke.class}) + @Test + public void webAccessSettingTest() { + onData(PreferenceMatchers.withTitle(R.string.preference_title_web_access)).perform(click()); + onView(withText(R.string.preference_screen_web_access_title)).check(matches(isDisplayed())); + + onView(withId(android.R.id.edit)).perform(typeText("domain.net")); + onView(withId(android.R.id.button2)).perform(click()); + + onData(PreferenceMatchers.withTitle(R.string.preference_title_web_access)).perform(click()); + onView(withId(android.R.id.button1)).perform(click()); + + onData(PreferenceMatchers.withTitle(R.string.preference_title_web_access)).perform(click()); + ViewActions.closeSoftKeyboard(); + onView(withId(android.R.id.button3)).perform(click()); + intended(expectedBrowserIntent); + } + private void checkPreference(int displayedTitleResourceString, String sharedPreferenceTag) { SharedPreferences sharedPreferences = PreferenceManager .getDefaultSharedPreferences(ApplicationProvider.getApplicationContext()); diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/dialog/DeleteLookDialogTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/dialog/DeleteLookDialogTest.java index a8fa16f213e..419f7cbee32 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/dialog/DeleteLookDialogTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/dialog/DeleteLookDialogTest.java @@ -51,6 +51,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import static org.catrobat.catroid.common.Constants.IMAGE_DIRECTORY_NAME; +import static org.catrobat.catroid.uiespresso.ui.actionbar.utils.ActionModeWrapper.onActionMode; import static org.catrobat.catroid.uiespresso.ui.fragment.rvutils.RecyclerViewInteractionWrapper.onRecyclerView; import static org.hamcrest.Matchers.allOf; @@ -141,9 +142,7 @@ public void cancelDeleteLookDialogTest() { onView(withText(toBeDeletedLookName)) .check(matches(isDisplayed())); - onView(withText(ApplicationProvider.getApplicationContext() - .getResources().getQuantityString(R.plurals.am_delete_looks_title, 1, 1))) - .check(matches(isDisplayed())); + onActionMode().checkTitleMatches(UiTestUtils.getResourcesString(R.string.delete) + " 1"); } private void createProject(String projectName) throws IOException { diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/dialog/DeleteSoundDialogTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/dialog/DeleteSoundDialogTest.java index fe2b40186c8..7f28eaa201b 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/dialog/DeleteSoundDialogTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/dialog/DeleteSoundDialogTest.java @@ -51,6 +51,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import static org.catrobat.catroid.common.Constants.SOUND_DIRECTORY_NAME; +import static org.catrobat.catroid.uiespresso.ui.actionbar.utils.ActionModeWrapper.onActionMode; import static org.catrobat.catroid.uiespresso.ui.fragment.rvutils.RecyclerViewInteractionWrapper.onRecyclerView; import static org.hamcrest.Matchers.allOf; @@ -140,9 +141,7 @@ public void cancelDeleteSoundDialogTest() { onView(withText(toBeDeletedSoundName)) .check(matches(isDisplayed())); - onView(withText(ApplicationProvider.getApplicationContext() - .getResources().getQuantityString(R.plurals.am_delete_sounds_title, 1, 1))) - .check(matches(isDisplayed())); + onActionMode().checkTitleMatches(UiTestUtils.getResourcesString(R.string.delete) + " 1"); } private void createProject(String projectName) throws IOException { diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/dialog/DeleteSpriteDialogTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/dialog/DeleteSpriteDialogTest.java index d88c38260df..3fdcd723300 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/dialog/DeleteSpriteDialogTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/dialog/DeleteSpriteDialogTest.java @@ -42,6 +42,7 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import static org.catrobat.catroid.uiespresso.ui.actionbar.utils.ActionModeWrapper.onActionMode; import static org.catrobat.catroid.uiespresso.ui.fragment.rvutils.RecyclerViewInteractionWrapper.onRecyclerView; import static org.hamcrest.Matchers.allOf; @@ -131,9 +132,7 @@ public void cancelDeleteSpriteDialogTest() { onView(withText(toBeDeletedSpriteName)) .check(matches(isDisplayed())); - onView(withText(ApplicationProvider.getApplicationContext() - .getResources().getQuantityString(R.plurals.am_delete_sprites_title, 1, 1))) - .check(matches(isDisplayed())); + onActionMode().checkTitleMatches(UiTestUtils.getResourcesString(R.string.delete) + " 1"); } private void createProject(String projectName) { diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/dialog/OrientationDialogTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/dialog/OrientationDialogTest.java index 5cee4c63a4a..69ea2fac5da 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/dialog/OrientationDialogTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/dialog/OrientationDialogTest.java @@ -104,7 +104,7 @@ public void tearDown() { @Test @Category({Level.Smoke.class, Cat.AppUi.class, Cat.Gadgets.class}) public void testCreateNewCastProject() { - onView(withText(R.string.main_menu_new)) + onView(withId(R.id.floating_action_button)) .perform(click()); onView(withText(R.string.new_project_dialog_title)) .check(matches(isDisplayed())); diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/RenameSpriteTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/RenameSpriteTest.java index 97ca5bce46b..b389e4bf960 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/RenameSpriteTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/RenameSpriteTest.java @@ -35,6 +35,7 @@ import org.catrobat.catroid.testsuites.annotations.Cat; import org.catrobat.catroid.testsuites.annotations.Level; import org.catrobat.catroid.ui.ProjectActivity; +import org.catrobat.catroid.uiespresso.util.UiTestUtils; import org.catrobat.catroid.uiespresso.util.rules.FragmentActivityTestRule; import org.junit.Before; import org.junit.Rule; @@ -45,6 +46,7 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import static org.catrobat.catroid.uiespresso.ui.actionbar.utils.ActionModeWrapper.onActionMode; import static org.catrobat.catroid.uiespresso.ui.fragment.rvutils.RecyclerViewInteractionWrapper.onRecyclerView; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.instanceOf; @@ -115,9 +117,7 @@ public void cancelRenameSpriteDialogTest() { onView(withText(secondSpriteName)) .check(matches(isDisplayed())); - onView(withText(ApplicationProvider.getApplicationContext() - .getResources().getQuantityString(R.plurals.am_rename_sprites_title, 1, 1))) - .check(matches(isDisplayed())); + onActionMode().checkTitleMatches(UiTestUtils.getResourcesString(R.string.rename)); } @Category({Cat.AppUi.class, Level.Smoke.class}) diff --git a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/AddUserBrickDataTest.java b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/UserDefinedBrickTest.java similarity index 80% rename from catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/AddUserBrickDataTest.java rename to catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/UserDefinedBrickTest.java index 8d98eca64a4..475dc98319b 100644 --- a/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/AddUserBrickDataTest.java +++ b/catroid/src/androidTest/java/org/catrobat/catroid/uiespresso/ui/fragment/UserDefinedBrickTest.java @@ -23,11 +23,16 @@ package org.catrobat.catroid.uiespresso.ui.fragment; import org.catrobat.catroid.R; +import org.catrobat.catroid.content.Script; +import org.catrobat.catroid.content.bricks.GlideToBrick; +import org.catrobat.catroid.content.bricks.UserDefinedBrick; import org.catrobat.catroid.test.utils.TestUtils; import org.catrobat.catroid.ui.SpriteActivity; +import org.catrobat.catroid.ui.recyclerview.fragment.ScriptFragment; import org.catrobat.catroid.uiespresso.content.brick.utils.BrickTestUtils; import org.catrobat.catroid.uiespresso.util.UiTestUtils; import org.catrobat.catroid.uiespresso.util.matchers.BrickCategoryListMatchers; +import org.catrobat.catroid.uiespresso.util.matchers.BrickPrototypeListMatchers; import org.catrobat.catroid.uiespresso.util.rules.FragmentActivityTestRule; import org.junit.After; import org.junit.Before; @@ -39,6 +44,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; +import static junit.framework.TestCase.assertTrue; + import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; @@ -56,7 +63,7 @@ import static androidx.test.espresso.matcher.ViewMatchers.withText; @RunWith(AndroidJUnit4.class) -public class AddUserBrickDataTest { +public class UserDefinedBrickTest { @Rule public FragmentActivityTestRule baseActivityTestRule = new @@ -65,12 +72,14 @@ public class AddUserBrickDataTest { @After public void tearDown() throws IOException { - TestUtils.deleteProjects(AddUserBrickDataTest.class.getSimpleName()); + TestUtils.deleteProjects(UserDefinedBrickTest.class.getSimpleName()); } @Before public void setUp() throws IOException { - BrickTestUtils.createProjectAndGetStartScript(AddUserBrickDataTest.class.getSimpleName()); + Script script = + BrickTestUtils.createProjectAndGetStartScript(UserDefinedBrickTest.class.getSimpleName()); + script.addBrick(new GlideToBrick()); baseActivityTestRule.launchActivity(); } @@ -143,6 +152,28 @@ public void testAddInputToUserBrickSameInput() { onView(withId(R.id.next)).check(matches(not(isEnabled()))); } + @Test + public void testAddUserDefinedBrickToScriptFragment() { + clickOnAddInputToUserBrick(); + onView(withId(R.id.next)) + .perform(click()); + onView(withId(R.id.confirm)) + .perform(click()); + onView(withId(R.id.fragment_script)).check(matches(isDisplayed())); + ScriptFragment scriptFragment = ((ScriptFragment) baseActivityTestRule.getActivity() + .getSupportFragmentManager().findFragmentByTag(ScriptFragment.TAG)); + assertTrue(scriptFragment.isCurrentlyMoving()); + onView(withId(R.id.fragment_script)).perform(click()); + + selectYourBricks(); + onData(allOf(is(instanceOf(UserDefinedBrick.class)))) + .inAdapterView(BrickPrototypeListMatchers.isBrickPrototypeView()) + .atPosition(0) + .perform(click()); + onView(withId(R.id.fragment_script)).check(matches(isDisplayed())); + assertTrue(scriptFragment.isCurrentlyMoving()); + } + private void selectYourBricks() { onView(withId(R.id.button_add)) .perform(click()); diff --git a/catroid/src/androidTest/res/raw/testsound2.mp3 b/catroid/src/androidTest/res/raw/testsound2.mp3 new file mode 100644 index 00000000000..9bbb77e62da Binary files /dev/null and b/catroid/src/androidTest/res/raw/testsound2.mp3 differ diff --git a/catroid/src/lunaAndCat/res/values/colors.xml b/catroid/src/lunaAndCat/res/values/colors.xml index e8598784765..882c0a960a6 100644 --- a/catroid/src/lunaAndCat/res/values/colors.xml +++ b/catroid/src/lunaAndCat/res/values/colors.xml @@ -42,4 +42,10 @@ #EAB7D5 #33B5E5 + + #200E3D + #C8A2CA + #EAB7D5 + #200E3D + diff --git a/catroid/src/main/assets/URL_whitelist.json b/catroid/src/main/assets/trustedDomains.json similarity index 100% rename from catroid/src/main/assets/URL_whitelist.json rename to catroid/src/main/assets/trustedDomains.json diff --git a/catroid/src/main/java/android/preference/TrustListEditorPreference.kt b/catroid/src/main/java/android/preference/TrustListEditorPreference.kt new file mode 100644 index 00000000000..e7026b4fcda --- /dev/null +++ b/catroid/src/main/java/android/preference/TrustListEditorPreference.kt @@ -0,0 +1,95 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2020 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 android.preference + +import android.app.AlertDialog +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import androidx.core.content.ContextCompat.startActivity +import org.catrobat.catroid.R +import org.catrobat.catroid.TrustedDomainManager +import org.catrobat.catroid.common.Constants + +class TrustListEditorPreference(context: Context, attrs: AttributeSet) : EditTextPreference(context, attrs) { + private val neutralButtonText = context.getString(R.string.brick_context_dialog_help) + + init { + setDialogTitle(R.string.preference_screen_web_access_title) + setPositiveButtonText(R.string.ok) + setNegativeButtonText(R.string.cancel) + } + + override fun showDialog(state: Bundle?) { + val mBuilder = AlertDialog.Builder(context) + .setTitle(dialogTitle) + .setIcon(dialogIcon) + .setPositiveButton(positiveButtonText) { _, _ -> + text = editText.text.toString() + TrustedDomainManager.setUserTrustList(text) + } + .setNeutralButton(neutralButtonText, null) + .setNegativeButton(negativeButtonText, this) + .setOnDismissListener(this) + + LayoutInflater.from(mBuilder.context).inflate(dialogLayoutResource, null)?.run { + onBindDialogView(this) + mBuilder.setView(this) + } ?: run { + mBuilder.setMessage(dialogMessage) + } + onPrepareDialogBuilder(mBuilder) + + mBuilder.create().apply { + state?.run { onRestoreInstanceState(this) } + window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) + editText.requestFocus() + setOnShowListener { + getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(Constants.WEB_REQUEST_WIKI_URL)) + startActivity(context, intent, null) + } + } + show() + } + } + + override fun onBindDialogView(view: View) { + super.onBindDialogView(view) + text = TrustedDomainManager.getUserTrustList() + editText.setText(text) + editText.setSelection(text.length) + val oldParent = editText.parent + if (oldParent !== view) { + (oldParent as? ViewGroup)?.removeView(editText) + onAddEditTextToDialogView(view, editText) + } + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/ProjectManager.java b/catroid/src/main/java/org/catrobat/catroid/ProjectManager.java index 3d67199a626..24e1bd3b5a4 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ProjectManager.java +++ b/catroid/src/main/java/org/catrobat/catroid/ProjectManager.java @@ -25,7 +25,6 @@ import android.content.Context; import android.util.Log; -import org.catrobat.catroid.common.Constants; import org.catrobat.catroid.common.DefaultProjectHandler; import org.catrobat.catroid.common.DefaultProjectHandler.ProjectCreatorType; import org.catrobat.catroid.common.LookData; @@ -57,18 +56,12 @@ import org.catrobat.catroid.io.XstreamSerializer; import org.catrobat.catroid.physics.PhysicsCollisionListener; import org.catrobat.catroid.ui.settingsfragments.SettingsFragment; -import org.catrobat.catroid.utils.Utils; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import java.util.regex.Pattern; import androidx.annotation.VisibleForTesting; @@ -79,7 +72,6 @@ public final class ProjectManager implements EagerSingleton { private static ProjectManager instance; private static final String TAG = ProjectManager.class.getSimpleName(); - private static Pattern urlWhitelistPattern; private Project project; private Scene currentlyEditedScene; @@ -313,43 +305,6 @@ public List getGlobalListConflicts(Project project1, Project project2) return conflicts; } - public static synchronized boolean checkIfURLIsWhitelisted(String url) { - if (urlWhitelistPattern == null) { - try { - initializeURLWhitelistPattern(); - } catch (IOException | JSONException | NullPointerException e) { - Log.e(TAG, "Cannot read URL whitelist", e); - return false; - } - } - return urlWhitelistPattern.matcher(url).matches(); - } - - private static void initializeURLWhitelistPattern() throws IOException, JSONException, NullPointerException { - InputStream stream = Utils.getInputStreamFromAsset(instance.applicationContext, Constants.URL_WHITELIST_JSON_FILE_NAME); - JSONObject whiteList = Utils.getJsonObjectFromInputStream(stream); - JSONArray domains = whiteList.getJSONArray(Constants.URL_WHITELIST_JSON_ARRAY_NAME); - - StringBuilder trustedDomains = new StringBuilder("("); - for (int i = 0; i < domains.length(); i++) { - trustedDomains.append(domains.getString(i)); - - if (i < domains.length() - 1) { - trustedDomains.append('|'); - } - } - trustedDomains.append(')'); - - urlWhitelistPattern = Pattern.compile("https?://([a-zA-Z0-9-]+\\.)*" - + trustedDomains.toString().replaceAll("\\.", "\\\\.") - + "(:[0-9]{1,5})?(/.*)?"); - } - - @VisibleForTesting - public static void resetURLWhitelistPattern() { - urlWhitelistPattern = null; - } - @VisibleForTesting public static void updateCollisionFormulasTo993(Project project) { for (Scene scene : project.getSceneList()) { @@ -528,7 +483,7 @@ public void setStartScene(Scene scene) { } public Scene getCurrentlyEditedScene() { - if (currentlyEditedScene == null) { + if (currentlyEditedScene == null && project != null) { currentlyEditedScene = project.getDefaultScene(); } return currentlyEditedScene; diff --git a/catroid/src/main/java/org/catrobat/catroid/TrustedDomainManager.kt b/catroid/src/main/java/org/catrobat/catroid/TrustedDomainManager.kt new file mode 100644 index 00000000000..5dfad685355 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/TrustedDomainManager.kt @@ -0,0 +1,185 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2020 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 + +import android.util.Log +import androidx.annotation.VisibleForTesting +import org.catrobat.catroid.common.Constants.JSON_INDENTATION +import org.catrobat.catroid.common.Constants.TRUSTED_DOMAINS_FILE_NAME +import org.catrobat.catroid.common.Constants.TRUSTED_USER_DOMAINS_FILE +import org.catrobat.catroid.common.Constants.TRUST_LIST_JSON_ARRAY_NAME +import org.catrobat.catroid.utils.Utils +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import java.io.IOException +import java.util.regex.Pattern + +object TrustedDomainManager { + private val TAG = TrustedDomainManager::class.java.simpleName + private const val READ_ERROR_LOG = "Cannot read trusted domains" + private const val PARSE_ERROR_LOG = "Cannot parse trusted domains" + + private val trustListPattern: Pattern? by lazy { + initializeTrustListPattern() + } + var userTrustListPattern: Pattern? = null + + @Synchronized + fun isURLTrusted(url: String): Boolean { + if (userTrustListPattern == null) { + userTrustListPattern = initializeUserTrustListPattern() + } + return trustListPattern?.matcher(url)?.matches() ?: false || + userTrustListPattern?.matcher(url)?.matches() ?: false + } + + fun addToUserTrustList(domain: String): Boolean { + try { + val domains = when { + TRUSTED_USER_DOMAINS_FILE.exists() -> + TRUSTED_USER_DOMAINS_FILE.inputStream().use { + val trustList = Utils.getJsonObjectFromInputStream(it) + trustList.getJSONArray(TRUST_LIST_JSON_ARRAY_NAME).put(cleanUpUserInput(domain)) + } + TRUSTED_USER_DOMAINS_FILE.createNewFile() -> JSONArray(listOf(cleanUpUserInput(domain))) + else -> return false + } + userTrustListPattern = getTrustListPatternFromDomains(domains) + val trustList = JSONObject(mapOf(TRUST_LIST_JSON_ARRAY_NAME to domains)) + TRUSTED_USER_DOMAINS_FILE.writeText(trustList.toString(JSON_INDENTATION)) + return true + } catch (e: IOException) { + Log.e(TAG, READ_ERROR_LOG, e) + return false + } catch (e: JSONException) { + Log.e(TAG, PARSE_ERROR_LOG, e) + return false + } + } + + fun getUserTrustList(): String { + return try { + if (TRUSTED_USER_DOMAINS_FILE.exists()) { + TRUSTED_USER_DOMAINS_FILE.inputStream().use { + val trustList = Utils.getJsonObjectFromInputStream(it) + val domains = trustList.getJSONArray(TRUST_LIST_JSON_ARRAY_NAME) + cleanUpUserInput(domains.join("\n")) + } + } else "" + } catch (e: IOException) { + Log.e(TAG, READ_ERROR_LOG, e) + "" + } catch (e: JSONException) { + Log.e(TAG, PARSE_ERROR_LOG, e) + "" + } + } + + fun setUserTrustList(domains: String): Boolean { + try { + return when { + domains.isBlank() -> resetUserTrustList() + TRUSTED_USER_DOMAINS_FILE.exists() || TRUSTED_USER_DOMAINS_FILE.createNewFile() -> { + val jsonArray = JSONArray(cleanUpUserInput(domains).split("\n")) + userTrustListPattern = getTrustListPatternFromDomains(jsonArray) + val trustList = JSONObject(mapOf(TRUST_LIST_JSON_ARRAY_NAME to jsonArray)) + TRUSTED_USER_DOMAINS_FILE.writeText(trustList.toString(JSON_INDENTATION)) + true + } + else -> false + } + } catch (e: IOException) { + Log.e(TAG, READ_ERROR_LOG, e) + return false + } catch (e: JSONException) { + Log.e(TAG, PARSE_ERROR_LOG, e) + return false + } + } + + private fun initializeTrustListPattern(): Pattern? { + try { + Utils.getInputStreamFromAsset( + CatroidApplication.getAppContext(), + TRUSTED_DOMAINS_FILE_NAME + ).use { + val trustList = Utils.getJsonObjectFromInputStream(it) + val domains = trustList.getJSONArray(TRUST_LIST_JSON_ARRAY_NAME) + return getTrustListPatternFromDomains(domains) + } + } catch (e: IOException) { + Log.e(TAG, READ_ERROR_LOG, e) + return null + } catch (e: JSONException) { + Log.e(TAG, PARSE_ERROR_LOG, e) + return null + } + } + + private fun initializeUserTrustListPattern(): Pattern? { + try { + if (!TRUSTED_USER_DOMAINS_FILE.exists()) { + return null + } + TRUSTED_USER_DOMAINS_FILE.inputStream().use { + val trustList = Utils.getJsonObjectFromInputStream(it) + val domains = trustList.getJSONArray(TRUST_LIST_JSON_ARRAY_NAME) + return getTrustListPatternFromDomains(domains) + } + } catch (e: IOException) { + Log.e(TAG, READ_ERROR_LOG, e) + return null + } catch (e: JSONException) { + Log.e(TAG, PARSE_ERROR_LOG, e) + return null + } + } + + private fun getTrustListPatternFromDomains(domains: JSONArray): Pattern { + val trustedDomains = StringBuilder("(") + for (i in 0 until domains.length()) { + trustedDomains.append(domains.getString(i)) + if (i < domains.length() - 1) { + trustedDomains.append('|') + } + } + trustedDomains.append(')') + + return Pattern.compile( + "https?://([a-zA-Z0-9-]+\\.)*" + + trustedDomains.toString().replace("\\.".toRegex(), "\\\\.") + + "(:[0-9]{1,5})?(/.*)?" + ) + } + + private fun cleanUpUserInput(string: String): String = + string.replace("[ \"{}\\[\\]]|(\\n){2,}".toRegex(), "").removeSuffix("\n") + + @VisibleForTesting + fun resetUserTrustList(): Boolean { + userTrustListPattern = null + return TRUSTED_USER_DOMAINS_FILE.delete() + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/common/Backpack.java b/catroid/src/main/java/org/catrobat/catroid/common/Backpack.java index 546bb5e952b..dea00e0871d 100644 --- a/catroid/src/main/java/org/catrobat/catroid/common/Backpack.java +++ b/catroid/src/main/java/org/catrobat/catroid/common/Backpack.java @@ -25,6 +25,7 @@ import org.catrobat.catroid.content.Scene; import org.catrobat.catroid.content.Script; import org.catrobat.catroid.content.Sprite; +import org.catrobat.catroid.content.bricks.UserDefinedBrick; import java.io.Serializable; import java.util.HashMap; @@ -42,4 +43,5 @@ public class Backpack implements Serializable { public List backpackedLooks = new CopyOnWriteArrayList<>(); public HashMap> backpackedScripts = new HashMap<>(); + public HashMap> backpackedUserDefinedBricks = new HashMap<>(); } diff --git a/catroid/src/main/java/org/catrobat/catroid/common/BrickValues.java b/catroid/src/main/java/org/catrobat/catroid/common/BrickValues.java index ea73e572cdc..8402c49daf7 100644 --- a/catroid/src/main/java/org/catrobat/catroid/common/BrickValues.java +++ b/catroid/src/main/java/org/catrobat/catroid/common/BrickValues.java @@ -84,6 +84,8 @@ public final class BrickValues { public static final int STOP_THIS_SCRIPT = 0; public static final int STOP_ALL_SCRIPTS = 1; public static final int STOP_OTHER_SCRIPTS = 2; + public static final int FOR_LOOP_FROM = 1; + public static final int FOR_LOOP_TO = 10; //Constants Lego public static final String LEGO_MOTOR = "A"; diff --git a/catroid/src/main/java/org/catrobat/catroid/common/Constants.java b/catroid/src/main/java/org/catrobat/catroid/common/Constants.java index c3b2e4a5a9d..006b880129d 100644 --- a/catroid/src/main/java/org/catrobat/catroid/common/Constants.java +++ b/catroid/src/main/java/org/catrobat/catroid/common/Constants.java @@ -35,10 +35,11 @@ import androidx.annotation.IntDef; import static org.catrobat.catroid.common.FlavoredConstants.BASE_URL_HTTPS; +import static org.catrobat.catroid.common.FlavoredConstants.DEFAULT_ROOT_DIRECTORY; public final class Constants { - public static final float CURRENT_CATROBAT_LANGUAGE_VERSION = 0.999993f; + public static final float CURRENT_CATROBAT_LANGUAGE_VERSION = 0.999997f; public static final String REMOTE_DISPLAY_APP_ID = "CEBB9229"; public static final int CAST_CONNECTION_TIMEOUT = 5000; //in milliseconds @@ -82,9 +83,12 @@ public final class Constants { public static final String BACKPACK_SOUND_DIRECTORY_NAME = "backpack_sound"; public static final String BACKPACK_IMAGE_DIRECTORY_NAME = "backpack_image"; - // Whitelist File - public static final String URL_WHITELIST_JSON_FILE_NAME = "URL_whitelist.json"; - public static final String URL_WHITELIST_JSON_ARRAY_NAME = "domains"; + // Trusted domains + public static final String TRUSTED_DOMAINS_FILE_NAME = "trustedDomains.json"; + public static final String TRUSTED_USER_DOMAINS_FILE_NAME = "trustedUserDomains.json"; + public static final String TRUST_LIST_JSON_ARRAY_NAME = "domains"; + public static final File TRUSTED_USER_DOMAINS_FILE = new File(DEFAULT_ROOT_DIRECTORY, TRUSTED_USER_DOMAINS_FILE_NAME); + public static final int JSON_INDENTATION = 4; // Temporary Files and Directories: public static final File CACHE_DIR = CatroidApplication.getAppContext().getCacheDir(); @@ -107,6 +111,9 @@ public final class Constants { private static final String WEB_TEST_URL = BuildConfig.WEB_TEST_URL; public static final String MAIN_URL_HTTPS = BuildConfig.WEB_TEST_FLAG ? WEB_TEST_URL : MAIN_URL_PRODUCTION; + // Default "flavor" in the web which equals "pocketcode" + public static final String BASE_APP_URL_HTTPS = MAIN_URL_HTTPS + "/app/"; + public static final String SHARE_PROGRAM_URL = BASE_URL_HTTPS + "/program/"; public static final String CATROBAT_ABOUT_URL = "https://www.catrobat.org/"; @@ -238,6 +245,7 @@ public final class Constants { //Various: public static final int BUFFER_8K = 8 * 1024; public static final String PREF_PROJECTNAME_KEY = "projectName"; + public static final int MAX_FILE_NAME_LENGTH = 127; public static final String COLLISION_PNG_META_TAG_KEY = "CollisionPolygonVertices"; public static final int COLLISION_VERTEX_LIMIT = 100; diff --git a/catroid/src/main/java/org/catrobat/catroid/common/DroneConfigPreference.java b/catroid/src/main/java/org/catrobat/catroid/common/DroneConfigPreference.java index d8cadf928b8..f8248a38902 100644 --- a/catroid/src/main/java/org/catrobat/catroid/common/DroneConfigPreference.java +++ b/catroid/src/main/java/org/catrobat/catroid/common/DroneConfigPreference.java @@ -22,6 +22,8 @@ */ package org.catrobat.catroid.common; +import org.catrobat.catroid.utils.EnumUtils; + public abstract class DroneConfigPreference { public enum Preferences { @@ -49,15 +51,8 @@ public static String getPreferenceCode(DroneConfigPreference.Preferences prefere } public static DroneConfigPreference.Preferences getPreferenceFromPreferenceCode(String preferenceCode) { - if (preferenceCode == null) { - return Preferences.FIRST; - } - - try { - return valueOf(preferenceCode); - } catch (IllegalArgumentException e) { - return Preferences.FIRST; - } + Preferences preferences = EnumUtils.getEnum(Preferences.class, preferenceCode); + return preferences != null ? preferences : Preferences.FIRST; } } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/ActionFactory.java b/catroid/src/main/java/org/catrobat/catroid/content/ActionFactory.java index 40701fe2a1a..00f5ede2b0a 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/ActionFactory.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/ActionFactory.java @@ -38,6 +38,7 @@ import org.catrobat.catroid.content.actions.AskAction; import org.catrobat.catroid.content.actions.AskSpeechAction; import org.catrobat.catroid.content.actions.AssertEqualsAction; +import org.catrobat.catroid.content.actions.AssertUserListAction; import org.catrobat.catroid.content.actions.CameraBrickAction; import org.catrobat.catroid.content.actions.ChangeBrightnessByNAction; import org.catrobat.catroid.content.actions.ChangeColorByNAction; @@ -74,6 +75,7 @@ import org.catrobat.catroid.content.actions.EventThread; import org.catrobat.catroid.content.actions.FinishStageAction; import org.catrobat.catroid.content.actions.FlashAction; +import org.catrobat.catroid.content.actions.ForVariableFromToAction; import org.catrobat.catroid.content.actions.GoNStepsBackAction; import org.catrobat.catroid.content.actions.GoToOtherSpritePositionAction; import org.catrobat.catroid.content.actions.GoToRandomPositionAction; @@ -122,6 +124,7 @@ import org.catrobat.catroid.content.actions.RaspiSendDigitalValueAction; import org.catrobat.catroid.content.actions.ReadListFromDeviceAction; import org.catrobat.catroid.content.actions.ReadVariableFromDeviceAction; +import org.catrobat.catroid.content.actions.ReadVariableFromFileAction; import org.catrobat.catroid.content.actions.RepeatAction; import org.catrobat.catroid.content.actions.RepeatUntilAction; import org.catrobat.catroid.content.actions.ReplaceItemInUserListAction; @@ -156,6 +159,7 @@ import org.catrobat.catroid.content.actions.StopAllSoundsAction; import org.catrobat.catroid.content.actions.StopOtherScriptsAction; import org.catrobat.catroid.content.actions.StopRunningStitchAction; +import org.catrobat.catroid.content.actions.StopSoundAction; import org.catrobat.catroid.content.actions.StopThisScriptAction; import org.catrobat.catroid.content.actions.StoreCSVIntoUserListAction; import org.catrobat.catroid.content.actions.TapAtAction; @@ -166,11 +170,13 @@ import org.catrobat.catroid.content.actions.VibrateAction; import org.catrobat.catroid.content.actions.WaitAction; import org.catrobat.catroid.content.actions.WaitForBubbleBrickAction; +import org.catrobat.catroid.content.actions.WaitForSoundAction; import org.catrobat.catroid.content.actions.WaitTillIdleAction; import org.catrobat.catroid.content.actions.WaitUntilAction; import org.catrobat.catroid.content.actions.WebRequestAction; import org.catrobat.catroid.content.actions.WriteListOnDeviceAction; import org.catrobat.catroid.content.actions.WriteVariableOnDeviceAction; +import org.catrobat.catroid.content.actions.WriteVariableToFileAction; import org.catrobat.catroid.content.actions.ZigZagStitchAction; import org.catrobat.catroid.content.actions.conditional.GlideToAction; import org.catrobat.catroid.content.actions.conditional.IfOnEdgeBounceAction; @@ -225,6 +231,14 @@ public Action createWaitAction(Sprite sprite, Formula delay) { return action; } + public Action createWaitForSoundAction(Sprite sprite, Formula delay, String soundFilePath) { + WaitForSoundAction action = action(WaitForSoundAction.class); + action.setSoundFilePath(soundFilePath); + action.setSprite(sprite); + action.setDelay(delay); + return action; + } + public Action createWaitForBubbleBrickAction(Sprite sprite, Formula delay) { WaitForBubbleBrickAction action = Actions.action(WaitForBubbleBrickAction.class); action.setSprite(sprite); @@ -550,6 +564,13 @@ public Action createPlaySoundAction(Sprite sprite, SoundInfo sound) { return action; } + public Action createStopSoundAction(Sprite sprite, SoundInfo sound) { + StopSoundAction action = Actions.action(StopSoundAction.class); + action.setSprite(sprite); + action.setSound(sound); + return action; + } + public Action createPointInDirectionAction(Sprite sprite, Formula degrees) { PointInDirectionAction action = Actions.action(PointInDirectionAction.class); action.setSprite(sprite); @@ -843,6 +864,16 @@ public Action createRepeatAction(Sprite sprite, Formula count, Action repeatedAc return action; } + public Action createForVariableFromToAction(Sprite sprite, UserVariable controlVariable, + Formula from, Formula to, Action repeatedAction) { + ForVariableFromToAction action = Actions.action(ForVariableFromToAction.class); + action.setRange(from, to); + action.setControlVariable(controlVariable); + action.setAction(repeatedAction); + action.setSprite(sprite); + return action; + } + public Action createWaitUntilAction(Sprite sprite, Formula condition) { WaitUntilAction action = Actions.action(WaitUntilAction.class); action.setSprite(sprite); @@ -1275,6 +1306,18 @@ public Action createAssertEqualsAction(Sprite sprite, Formula actual, Formula ex return action; } + public Action createAssertUserListsAction(Sprite sprite, UserList actual, UserList expected, + String position) { + AssertUserListAction action = action(AssertUserListAction.class); + action.setActual(actual); + action.setExpected(expected); + + action.setSprite(sprite); + action.setPosition(position); + + return action; + } + public Action createFinishStageAction() { FinishStageAction action = action(FinishStageAction.class); return action; @@ -1294,6 +1337,26 @@ public Action createWriteVariableOnDeviceAction(UserVariable userVariable) { return action; } + public Action createWriteVariableToFileAction(Sprite sprite, Formula variableFormula, UserVariable userVariable) { + WriteVariableToFileAction action = Actions.action(WriteVariableToFileAction.class); + action.setSprite(sprite); + action.setUserVariable(userVariable); + action.setFormula(variableFormula); + + return action; + } + + public Action createReadVariableFromFileAction( + Sprite sprite, Formula variableFormula, UserVariable userVariable, boolean deleteFile) { + ReadVariableFromFileAction action = Actions.action(ReadVariableFromFileAction.class); + action.setSprite(sprite); + action.setUserVariable(userVariable); + action.setFormula(variableFormula); + action.setDeleteFile(deleteFile); + + return action; + } + public Action createWriteListOnDeviceAction(UserList userList) { WriteListOnDeviceAction action = Actions.action(WriteListOnDeviceAction.class); action.setUserList(userList); diff --git a/catroid/src/main/java/org/catrobat/catroid/content/GroupSprite.java b/catroid/src/main/java/org/catrobat/catroid/content/GroupSprite.java index 6624351ea25..b67fa2366f0 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/GroupSprite.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/GroupSprite.java @@ -71,18 +71,17 @@ public void setCollapsed(boolean collapsed) { } } - public static List getSpritesFromGroupWithGroupName(String groupName) { - List result = new ArrayList(); - List spriteList = ProjectManager.getInstance().getCurrentlyPlayingScene().getSpriteList(); + public static List getSpritesFromGroupWithGroupName(String groupName, List sprites) { + List result = new ArrayList<>(); int position = 0; - for (Sprite sprite : spriteList) { + for (Sprite sprite : sprites) { if (groupName.equals(sprite.getName())) { break; } position++; } - for (int childPosition = position + 1; childPosition < spriteList.size(); childPosition++) { - Sprite spriteToCheck = spriteList.get(childPosition); + for (int childPosition = position + 1; childPosition < sprites.size(); childPosition++) { + Sprite spriteToCheck = sprites.get(childPosition); if (spriteToCheck instanceof GroupItemSprite) { result.add(spriteToCheck); } else { @@ -95,7 +94,8 @@ public static List getSpritesFromGroupWithGroupName(String groupName) { @Override public void createCollisionPolygons() { Log.i("GroupSprite", "Creating Collision Polygons for all Sprites of group!"); - List groupSprites = getSpritesFromGroupWithGroupName(getName()); + List spriteList = ProjectManager.getInstance().getCurrentlyPlayingScene().getSpriteList(); + List groupSprites = getSpritesFromGroupWithGroupName(getName(), spriteList); for (Sprite sprite : groupSprites) { for (LookData lookData : sprite.getLookList()) { lookData.getCollisionInformation().calculate(); diff --git a/catroid/src/main/java/org/catrobat/catroid/content/Look.java b/catroid/src/main/java/org/catrobat/catroid/content/Look.java index c239b993f38..037893501f9 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/Look.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/Look.java @@ -45,6 +45,7 @@ import org.catrobat.catroid.common.ThreadScheduler; import org.catrobat.catroid.content.actions.EventThread; import org.catrobat.catroid.content.eventids.EventId; +import org.catrobat.catroid.sensing.CollisionInformation; import org.catrobat.catroid.utils.TouchUtil; import java.lang.annotation.Retention; @@ -618,10 +619,11 @@ public Polygon[] getCurrentCollisionPolygon() { if (getLookData() == null) { originalPolygons = new Polygon[0]; } else { - if (getLookData().getCollisionInformation().collisionPolygons == null) { - getLookData().getCollisionInformation().loadCollisionPolygon(); + CollisionInformation collisionInformation = getLookData().getCollisionInformation(); + if (collisionInformation.collisionPolygons == null) { + collisionInformation.loadCollisionPolygon(); } - originalPolygons = getLookData().getCollisionInformation().collisionPolygons; + originalPolygons = collisionInformation.collisionPolygons; } Polygon[] transformedPolygons = new Polygon[originalPolygons.length]; diff --git a/catroid/src/main/java/org/catrobat/catroid/content/MediaPlayerWithSoundDetails.java b/catroid/src/main/java/org/catrobat/catroid/content/MediaPlayerWithSoundDetails.java new file mode 100644 index 00000000000..6740233a981 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/content/MediaPlayerWithSoundDetails.java @@ -0,0 +1,64 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2020 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.content; + +import android.media.MediaPlayer; + +public class MediaPlayerWithSoundDetails extends MediaPlayer { + private Sprite startedBySprite = null; + private String pathToSoundFile = null; + + public Sprite getStartedBySprite() { + return startedBySprite; + } + + public void setStartedBySprite(Sprite startedBySprite) { + this.startedBySprite = startedBySprite; + } + + public String getPathToSoundFile() { + return pathToSoundFile; + } + + public void setPathToSoundFile(String pathToSoundFile) { + this.pathToSoundFile = pathToSoundFile; + } + + @Override + public void release() { + super.release(); + clearData(); + } + + @Override + public void reset() { + super.reset(); + clearData(); + } + + private void clearData() { + startedBySprite = null; + pathToSoundFile = null; + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/Project.java b/catroid/src/main/java/org/catrobat/catroid/content/Project.java index 28098936d58..4d3a9ef3918 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/Project.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/Project.java @@ -25,6 +25,7 @@ import android.content.Context; import android.os.Build; +import com.badlogic.gdx.math.Rectangle; import com.thoughtworks.xstream.annotations.XStreamAlias; import org.catrobat.catroid.R; @@ -302,6 +303,12 @@ public XmlHeader getXmlHeader() { return this.xmlHeader; } + public Rectangle getScreenRectangle() { + int virtualScreenWidth = xmlHeader.virtualScreenWidth; + int virtualScreenHeight = xmlHeader.virtualScreenHeight; + return new Rectangle(-virtualScreenWidth / 2, -virtualScreenHeight / 2, virtualScreenWidth, virtualScreenHeight); + } + public Brick.ResourcesSet getRequiredResources() { Brick.ResourcesSet resourcesSet = new Brick.ResourcesSet(); diff --git a/catroid/src/main/java/org/catrobat/catroid/content/SoundBackup.java b/catroid/src/main/java/org/catrobat/catroid/content/SoundBackup.java new file mode 100644 index 00000000000..1953c5c7dfe --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/content/SoundBackup.java @@ -0,0 +1,48 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2020 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.content; + +public class SoundBackup { + private final String pathToSoundFile; + private final Sprite startedBySprite; + private final int currentPosition; + + public SoundBackup(String pathToSoundFile, Sprite startedBySprite, int currentPosition) { + this.pathToSoundFile = pathToSoundFile; + this.startedBySprite = startedBySprite; + this.currentPosition = currentPosition; + } + + public String getPathToSoundFile() { + return pathToSoundFile; + } + + public Sprite getStartedBySprite() { + return startedBySprite; + } + + public int getCurrentPosition() { + return currentPosition; + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/SoundFilePathWithSprite.java b/catroid/src/main/java/org/catrobat/catroid/content/SoundFilePathWithSprite.java new file mode 100644 index 00000000000..bcea923cacb --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/content/SoundFilePathWithSprite.java @@ -0,0 +1,52 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2020 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.content; + +import androidx.annotation.Nullable; + +public class SoundFilePathWithSprite { + private final String soundFilePath; + private final Sprite sprite; + + public SoundFilePathWithSprite(String soundFilePath, Sprite sprite) { + this.soundFilePath = soundFilePath; + this.sprite = sprite; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj instanceof SoundFilePathWithSprite) { + SoundFilePathWithSprite soundFilePathWithSprite = (SoundFilePathWithSprite) obj; + return soundFilePathWithSprite.soundFilePath.equals(soundFilePath) + && soundFilePathWithSprite.sprite == sprite; + } + return false; + } + + @Override + public int hashCode() { + int result = sprite.hashCode(); + return (31 * result + soundFilePath.hashCode()); + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/Sprite.java b/catroid/src/main/java/org/catrobat/catroid/content/Sprite.java index 9c0a486ba50..7c97d9b6e23 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/Sprite.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/Sprite.java @@ -58,6 +58,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.UUID; @XStreamFieldKeyOrder({ "name", @@ -66,7 +67,8 @@ "scriptList", "nfcTagList", "userVariables", - "userLists" + "userLists", + "userDefinedBrickList" }) public class Sprite implements Cloneable, Nameable, Serializable { @@ -90,7 +92,7 @@ public class Sprite implements Cloneable, Nameable, Serializable { private List nfcTagList = new ArrayList<>(); private List userVariables = new ArrayList<>(); private List userLists = new ArrayList<>(); - private transient List userDefinedBrickList = new ArrayList<>(); + private List userDefinedBrickList = new ArrayList<>(); private transient ActionFactory actionFactory = new ActionFactory(); @@ -139,8 +141,45 @@ public List getUserDefinedBrickList() { return userDefinedBrickList; } - public boolean addUserDefinedBrick(UserDefinedBrick userDefinedBrick) { - return userDefinedBrickList.add(userDefinedBrick); + public UserDefinedBrick getUserDefinedBrickWithSameUserData(UserDefinedBrick userDefinedBrick) { + if (userDefinedBrick == null) { + return null; + } + for (Brick brick: userDefinedBrickList) { + if (((UserDefinedBrick) brick).isUserDefinedBrickDataEqual(userDefinedBrick)) { + return (UserDefinedBrick) brick; + } + } + return null; + } + + public UserDefinedBrick getUserDefinedBrickByID(UUID userDefinedBrickID) { + for (Brick brick : userDefinedBrickList) { + if (((UserDefinedBrick) brick).getUserDefinedBrickID().equals(userDefinedBrickID)) { + return (UserDefinedBrick) brick; + } + } + return null; + } + + private boolean containsUserDefinedBrickWithSameUserData(UserDefinedBrick userDefinedBrick) { + return getUserDefinedBrickWithSameUserData(userDefinedBrick) != null; + } + + public void addUserDefinedBrick(UserDefinedBrick userDefinedBrick) { + userDefinedBrickList.add(userDefinedBrick); + } + + public void addClonesOfUserDefinedBrickList(List userDefinedBricks) { + for (UserDefinedBrick userDefinedBrick : userDefinedBricks) { + if (!containsUserDefinedBrickWithSameUserData(userDefinedBrick)) { + try { + addUserDefinedBrick((UserDefinedBrick) userDefinedBrick.clone()); + } catch (CloneNotSupportedException e) { + Log.e(TAG, Log.getStackTraceString(e)); + } + } + } } public List getUserVariables() { @@ -450,7 +489,7 @@ public void createCollisionPolygons() { public static boolean doesUserBrickAlreadyExist(UserDefinedBrick userDefinedBrick, Sprite sprite) { for (Brick alreadyDefinedBrick : sprite.getUserDefinedBrickList()) { - if (alreadyDefinedBrick.equals(userDefinedBrick)) { + if (((UserDefinedBrick) alreadyDefinedBrick).isUserDefinedBrickDataEqual(userDefinedBrick)) { return true; } } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/UserDefinedScript.java b/catroid/src/main/java/org/catrobat/catroid/content/UserDefinedScript.java new file mode 100644 index 00000000000..b127ae18e0f --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/content/UserDefinedScript.java @@ -0,0 +1,63 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2020 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.content; + +import org.catrobat.catroid.content.bricks.ScriptBrick; +import org.catrobat.catroid.content.bricks.UserDefinedReceiverBrick; +import org.catrobat.catroid.content.eventids.EventId; + +import java.util.UUID; + +import androidx.annotation.VisibleForTesting; + +public class UserDefinedScript extends Script { + + private static final long serialVersionUID = 1L; + private UUID userDefinedBrickID; + + public UserDefinedScript(UUID userDefinedBrickID) { + this.userDefinedBrickID = userDefinedBrickID; + } + + @VisibleForTesting + public UserDefinedScript() { + } + + @Override + public ScriptBrick getScriptBrick() { + if (scriptBrick == null) { + scriptBrick = new UserDefinedReceiverBrick(this); + } + return scriptBrick; + } + + @Override + public EventId createEventId(Sprite sprite) { + return null; + } + + public UUID getUserDefinedBrickID() { + return userDefinedBrickID; + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/actions/AskAction.java b/catroid/src/main/java/org/catrobat/catroid/content/actions/AskAction.java deleted file mode 100644 index e043659f0e7..00000000000 --- a/catroid/src/main/java/org/catrobat/catroid/content/actions/AskAction.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Catroid: An on-device visual programming system for Android devices - * Copyright (C) 2010-2018 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.content.actions; - -import android.os.Message; -import android.util.Log; - -import com.badlogic.gdx.scenes.scene2d.Action; - -import org.catrobat.catroid.content.Sprite; -import org.catrobat.catroid.formulaeditor.Formula; -import org.catrobat.catroid.formulaeditor.InterpretationException; -import org.catrobat.catroid.formulaeditor.UserVariable; -import org.catrobat.catroid.stage.StageActivity; - -import java.util.ArrayList; - -public class AskAction extends Action { - - private Sprite sprite; - private Formula questionFormula; - private UserVariable answerVariable; - - private boolean questionAsked = false; - private boolean answerReceived = false; - - private void askQuestion() { - if (StageActivity.messageHandler == null) { - return; - } - String question = ""; - try { - if (questionFormula != null) { - question = questionFormula.interpretString(sprite); - } - } catch (InterpretationException e) { - Log.e(getClass().getSimpleName(), "formula interpretation in ask brick failed"); - } - - ArrayList params = new ArrayList<>(); - params.add(this); - params.add(question); - Message message = StageActivity.messageHandler.obtainMessage(StageActivity.ASK_MESSAGE, params); - message.sendToTarget(); - - questionAsked = true; - } - - public void setAnswerText(String answer) { - if (answerVariable == null) { - return; - } - answerVariable.setValue(answer); - - answerReceived = true; - } - - public void setAnswerVariable(UserVariable answerVariable) { - if (answerVariable == null) { - return; - } - this.answerVariable = answerVariable; - } - - public void setQuestionFormula(Formula questionFormula) { - this.questionFormula = questionFormula; - } - - public void setSprite(Sprite sprite) { - this.sprite = sprite; - } - - @Override - public boolean act(float delta) { - if (!questionAsked) { - askQuestion(); - } - - return answerReceived; - } - - @Override - public void restart() { - questionAsked = false; - answerReceived = false; - super.restart(); - } -} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/actions/AskAction.kt b/catroid/src/main/java/org/catrobat/catroid/content/actions/AskAction.kt new file mode 100644 index 00000000000..0c7d8a1b7f0 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/content/actions/AskAction.kt @@ -0,0 +1,79 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2018 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.content.actions + +import android.util.Log +import com.badlogic.gdx.scenes.scene2d.Action +import org.catrobat.catroid.content.Sprite +import org.catrobat.catroid.formulaeditor.Formula +import org.catrobat.catroid.formulaeditor.InterpretationException +import org.catrobat.catroid.formulaeditor.UserVariable +import org.catrobat.catroid.stage.BrickDialogManager +import org.catrobat.catroid.stage.StageActivity + +class AskAction : Action() { + private var sprite: Sprite? = null + var questionFormula: Formula? = null + var answerVariable: UserVariable? = null + var questionAsked = false + private var answerReceived = false + private fun askQuestion() { + StageActivity.messageHandler ?: return + var question = "" + try { + question = questionFormula?.interpretString(sprite) ?: "" + } catch (e: InterpretationException) { + Log.e( + javaClass.simpleName, + "formula interpretation in ask brick failed" + ) + } + + val params = arrayListOf(BrickDialogManager.DialogType.ASK_DIALOG, this, question) + StageActivity.messageHandler.obtainMessage(StageActivity.SHOW_DIALOG, params).sendToTarget() + questionAsked = true + } + + fun setAnswerText(answer: String) { + answerVariable ?: return + answerVariable!!.value = answer + answerReceived = true + } + + fun setSprite(sprite: Sprite) { + this.sprite = sprite + } + + override fun act(delta: Float): Boolean { + if (!questionAsked) { + askQuestion() + } + return answerReceived + } + + override fun restart() { + questionAsked = false + answerReceived = false + super.restart() + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/actions/AssertAction.java b/catroid/src/main/java/org/catrobat/catroid/content/actions/AssertAction.java new file mode 100644 index 00000000000..476d39fafcf --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/content/actions/AssertAction.java @@ -0,0 +1,88 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2018 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.content.actions; + +import com.badlogic.gdx.scenes.scene2d.Action; + +import org.catrobat.catroid.content.Sprite; +import org.catrobat.catroid.stage.StageActivity; +import org.catrobat.catroid.stage.TestResult; + +import static org.catrobat.catroid.stage.TestResult.STAGE_ACTIVITY_TEST_FAIL; + +public abstract class AssertAction extends Action { + + protected String position; + protected Sprite sprite; + protected String assertTitle = "\nAssertEqualsError\n"; + + protected void failWith(String message) { + StageActivity.finishTestWithResult( + new TestResult(formattedPosition() + + assertTitle + message, STAGE_ACTIVITY_TEST_FAIL)); + } + + protected boolean equalValues(String actual, String expected) { + try { + return actual.equals(expected) || Double.parseDouble(actual) == Double.parseDouble(expected); + } catch (NumberFormatException numberFormatException) { + return false; + } + } + + protected int indexOfDifference(CharSequence actual, CharSequence expected) { + if (actual == null || expected == null) { + return 0; + } + int position; + for (position = 0; position < actual.length() && position < expected.length(); ++position) { + if (actual.charAt(position) != expected.charAt(position)) { + break; + } + } + if (position < expected.length() || position < actual.length()) { + return position; + } + return 0; + } + + protected String generateIndicator(Object actual, Object expected) { + int errorPosition = indexOfDifference(actual.toString(), expected.toString()); + + return String.valueOf(new char[errorPosition]).replace('\0', '-') + + "^"; + } + + private String formattedPosition() { + return "on sprite \"" + sprite.getName() + "\"\n" + position; + } + + public void setSprite(Sprite sprite) { + this.sprite = sprite; + } + + public void setPosition(String position) { + this.position = position; + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/actions/AssertEqualsAction.java b/catroid/src/main/java/org/catrobat/catroid/content/actions/AssertEqualsAction.java index 8dcf756e59b..f7c2f590f80 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/actions/AssertEqualsAction.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/actions/AssertEqualsAction.java @@ -23,25 +23,17 @@ package org.catrobat.catroid.content.actions; -import com.badlogic.gdx.scenes.scene2d.Action; - -import org.catrobat.catroid.content.Sprite; import org.catrobat.catroid.formulaeditor.Formula; -import org.catrobat.catroid.stage.StageActivity; -import org.catrobat.catroid.stage.TestResult; - -import static org.catrobat.catroid.stage.TestResult.STAGE_ACTIVITY_TEST_FAIL; -public class AssertEqualsAction extends Action { +public class AssertEqualsAction extends AssertAction { private Formula actualFormula = null; private Formula expectedFormula = null; - private String position; - private Sprite sprite; - private static final String ASSERT_EQUALS_ERROR = "\nAssertEqualsError\n"; @Override public boolean act(float delta) { + assertTitle = "\nAssertEqualsError\n"; + if (actualFormula == null) { failWith("Actual is null"); return false; @@ -61,30 +53,9 @@ public boolean act(float delta) { return true; } - private boolean equalValues(String actual, String expected) { - try { - return actual.equals(expected) || Double.parseDouble(actual) == Double.parseDouble(expected); - } catch (NumberFormatException numberFormatException) { - return false; - } - } - - private void failWith(String message) { - StageActivity.finishTestWithResult( - new TestResult(formattedPosition() - + ASSERT_EQUALS_ERROR + message, STAGE_ACTIVITY_TEST_FAIL)); - } - private String formattedAssertEqualsError(Object actual, Object expected) { - return "expected:<" + expected + "> but was:<" + actual + ">"; - } - - private String formattedPosition() { - return "on sprite \"" + sprite.getName() + "\"\n" + position; - } - - public void setSprite(Sprite sprite) { - this.sprite = sprite; + String indicator = generateIndicator(actual, expected); + return "expected: <" + expected + ">\nactual: <" + actual + ">\ndeviation: " + indicator; } public void setActual(Formula actual) { diff --git a/catroid/src/main/java/org/catrobat/catroid/content/actions/AssertUserListAction.java b/catroid/src/main/java/org/catrobat/catroid/content/actions/AssertUserListAction.java new file mode 100644 index 00000000000..31e1ce2d192 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/content/actions/AssertUserListAction.java @@ -0,0 +1,90 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2018 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.content.actions; + +import org.catrobat.catroid.formulaeditor.UserList; + +import java.util.List; + +public class AssertUserListAction extends AssertAction { + + private UserList actualUserList = null; + private UserList expectedUserList = null; + + @Override + public boolean act(float delta) { + assertTitle = "\nAssertUserListError\n"; + String message = ""; + + if (actualUserList == null) { + failWith("Actual list is null"); + return false; + } + if (expectedUserList == null) { + failWith("Expected list is null"); + return false; + } + + List actualList = actualUserList.getValue(); + List expectedList = expectedUserList.getValue(); + if (actualList.size() != expectedList.size()) { + message = "The number of list elements are not equal\nexpected: " + expectedList.size() + + " element/s\nactual: " + actualList.size() + " element/s\n\n"; + } + + for (int listPosition = 0; listPosition < expectedList.size(); listPosition++) { + try { + if (!equalValues(actualList.get(listPosition).toString(), + expectedList.get(listPosition).toString())) { + message = message.concat(formattedAssertError(listPosition, + actualList.get(listPosition), + expectedList.get(listPosition))); + } + } catch (IndexOutOfBoundsException e) { + break; + } + } + + if (message.isEmpty()) { + return true; + } else { + failWith(message); + return false; + } + } + + private String formattedAssertError(int listPosition, Object actual, Object expected) { + String indicator = generateIndicator(actual, expected); + return "position: " + listPosition + "\nexpected: <" + expected + ">\nactual: <" + + actual + ">\ndeviation: " + indicator + "\n\n"; + } + + public void setActual(UserList actual) { + this.actualUserList = actual; + } + + public void setExpected(UserList expected) { + this.expectedUserList = expected; + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/actions/ChangeVariableAction.java b/catroid/src/main/java/org/catrobat/catroid/content/actions/ChangeVariableAction.java deleted file mode 100644 index 5ebc77bf409..00000000000 --- a/catroid/src/main/java/org/catrobat/catroid/content/actions/ChangeVariableAction.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Catroid: An on-device visual programming system for Android devices - * Copyright (C) 2010-2018 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.content.actions; - -import android.util.Log; - -import com.badlogic.gdx.scenes.scene2d.Action; - -import org.catrobat.catroid.content.Sprite; -import org.catrobat.catroid.formulaeditor.Formula; -import org.catrobat.catroid.formulaeditor.UserVariable; - -public class ChangeVariableAction extends Action { - - private Sprite sprite; - private Formula changeVariable; - private UserVariable userVariable; - - @Override - public boolean act(float delta) { - if (userVariable == null) { - return true; - } - Object originalValue = userVariable.getValue(); - Object value = changeVariable == null ? 0d : changeVariable.interpretObject(sprite); - - if (originalValue instanceof String) { - return true; - } - - try { - if (value instanceof String) { - value = Double.valueOf((String) value); - } - } catch (NumberFormatException numberFormatException) { - Log.d(getClass().getSimpleName(), "Couldn't parse String", numberFormatException); - } - - if (originalValue instanceof Double && value instanceof Double) { - originalValue = ((Double) originalValue).isNaN() ? 0d : originalValue; - value = ((Double) value).isNaN() ? 0d : value; - userVariable.setValue(((Double) originalValue) + ((Double) value)); - return true; - } - return true; - } - - public void setUserVariable(UserVariable userVariable) { - this.userVariable = userVariable; - } - - public void setChangeVariable(Formula changeVariable) { - this.changeVariable = changeVariable; - } - - public void setSprite(Sprite sprite) { - this.sprite = sprite; - } -} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/actions/ChangeVariableAction.kt b/catroid/src/main/java/org/catrobat/catroid/content/actions/ChangeVariableAction.kt new file mode 100644 index 00000000000..bb7b89321e2 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/content/actions/ChangeVariableAction.kt @@ -0,0 +1,49 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2018 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.content.actions + +import com.badlogic.gdx.scenes.scene2d.Action +import org.catrobat.catroid.content.Sprite +import org.catrobat.catroid.formulaeditor.Formula +import org.catrobat.catroid.formulaeditor.UserVariable + +class ChangeVariableAction : Action() { + var sprite: Sprite? = null + var changeVariable: Formula? = null + var userVariable: UserVariable? = null + + override fun act(delta: Float): Boolean { + val originalValue = userVariable?.value as? Double ?: return true + val value = changeVariable?.interpretObject(sprite) ?: 0.0 + (value as? Double ?: (value as? String)?.toDoubleOrNull())?.run { + updateUserVariable(originalValue, this) + } + return true + } + + private fun updateUserVariable(originalValue: Double, value: Double) { + val original = originalValue.takeUnless { it.isNaN() } ?: 0.0 + val valueToAdd = value.takeUnless { it.isNaN() } ?: 0.0 + userVariable?.value = original + valueToAdd + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/actions/RepeatUntilAction.java b/catroid/src/main/java/org/catrobat/catroid/content/actions/ForVariableFromToAction.java similarity index 58% rename from catroid/src/main/java/org/catrobat/catroid/content/actions/RepeatUntilAction.java rename to catroid/src/main/java/org/catrobat/catroid/content/actions/ForVariableFromToAction.java index 1299d85f4af..8ab3cf93abe 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/actions/RepeatUntilAction.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/actions/ForVariableFromToAction.java @@ -1,6 +1,6 @@ /* * Catroid: An on-device visual programming system for Android devices - * Copyright (C) 2010-2018 The Catrobat Team + * Copyright (C) 2010-2020 The Catrobat Team * () * * This program is free software: you can redistribute it and/or modify @@ -20,6 +20,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ + package org.catrobat.catroid.content.actions; import android.util.Log; @@ -27,92 +28,92 @@ import org.catrobat.catroid.content.Sprite; import org.catrobat.catroid.formulaeditor.Formula; import org.catrobat.catroid.formulaeditor.InterpretationException; +import org.catrobat.catroid.formulaeditor.UserVariable; -public class RepeatUntilAction extends com.badlogic.gdx.scenes.scene2d.actions.RepeatAction { +public class ForVariableFromToAction extends com.badlogic.gdx.scenes.scene2d.actions.RepeatAction { - private int executedCount = 0; + private UserVariable controlVariable; + private Formula from; + private Formula to; private Sprite sprite; private boolean isCurrentLoopInitialized = false; + private boolean isRepeatActionInitialized = false; + private int fromValue; + private int toValue; private static final float LOOP_DELAY = 0.02f; private float currentTime = 0f; - private Formula repeatCondition; - - boolean isValidConditionFormula() { - try { - if (repeatCondition == null) { - return false; - } - - repeatCondition.interpretDouble(sprite); - } catch (InterpretationException interpretationException) { - Log.d(getClass().getSimpleName(), "Formula interpretation for this specific Brick failed.", - interpretationException); - return false; - } - - return true; - } - - boolean isConditionTrue() { - try { - return repeatCondition.interpretDouble(sprite).intValue() != 0; - } catch (InterpretationException interpretationException) { - Log.d(getClass().getSimpleName(), "Formula interpretation for this specific Brick failed.", - interpretationException); - return true; - } - } + private int executedCount = 0; + private int step = 1; @Override public boolean delegate(float delta) { - if (!isValidConditionFormula()) { + if (!isRepeatActionInitialized && !interpretParameters()) { return true; } if (!isCurrentLoopInitialized) { - if (isConditionTrue()) { - return true; - } currentTime = 0f; isCurrentLoopInitialized = true; } + setControlVariable(fromValue + step * executedCount); currentTime += delta; - if (action.act(delta) && currentTime >= LOOP_DELAY) { - + if (action != null && action.act(delta) && currentTime >= LOOP_DELAY) { executedCount++; - if (isConditionTrue()) { + + if (Math.abs(step * executedCount) > Math.abs(toValue - fromValue)) { return true; } isCurrentLoopInitialized = false; - - if (action != null) { - action.restart(); - } + action.restart(); } - return false; } @Override public void restart() { isCurrentLoopInitialized = false; + isRepeatActionInitialized = false; executedCount = 0; super.restart(); } - public void setRepeatCondition(Formula repeatCondition) { - this.repeatCondition = repeatCondition; - } - public void setSprite(Sprite sprite) { this.sprite = sprite; } - public int getExecutedCount() { - return executedCount; + public void setRange(Formula from, Formula to) { + this.from = from; + this.to = to; + } + + public void setControlVariable(UserVariable variable) { + controlVariable = variable; + } + + private boolean interpretParameters() { + isRepeatActionInitialized = true; + try { + Double fromInterpretation = from == null ? Double.valueOf(0d) : from.interpretDouble(sprite); + fromValue = fromInterpretation.intValue(); + Double toInterpretation = to == null ? Double.valueOf(0d) : to.interpretDouble(sprite); + toValue = toInterpretation.intValue(); + setStepValue(); + return true; + } catch (InterpretationException interpretationException) { + Log.d(getClass().getSimpleName(), "Formula interpretation for this specific Brick failed.", interpretationException); + return false; + } + } + + private void setStepValue() { + step = (fromValue <= toValue) ? 1 : -1; + } + + private void setControlVariable(int value) { + controlVariable.setValue((double) value); } } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/actions/LookRequestAction.kt b/catroid/src/main/java/org/catrobat/catroid/content/actions/LookRequestAction.kt index 801b2b74e1d..0b8179593d6 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/actions/LookRequestAction.kt +++ b/catroid/src/main/java/org/catrobat/catroid/content/actions/LookRequestAction.kt @@ -77,7 +77,7 @@ open class LookRequestAction : WebAction() { private fun handleInvalidFormat() { CatroidApplication.getAppContext()?.let { - showToastMessage(it.getString(R.string.brick_look_request_type_error_message, url)) + showToastMessage(it.getString(R.string.look_request_type_error_message, url)) } } @@ -89,7 +89,7 @@ open class LookRequestAction : WebAction() { override fun handleError(error: String) { errorCode = error CatroidApplication.getAppContext()?.let { - showToastMessage(it.getString(R.string.brick_look_request_http_error_message, url, errorCode)) + showToastMessage(it.getString(R.string.look_request_http_error_message, url, errorCode)) } } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/actions/PlaySoundAction.java b/catroid/src/main/java/org/catrobat/catroid/content/actions/PlaySoundAction.java index edd95fa4dd3..5b206850fef 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/actions/PlaySoundAction.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/actions/PlaySoundAction.java @@ -36,7 +36,7 @@ public class PlaySoundAction extends TemporalAction { @Override protected void update(float percent) { if (sound != null && sprite.getSoundList().contains(sound) && sound.getFile() != null) { - SoundManager.getInstance().playSoundFile(sound.getFile().getAbsolutePath()); + SoundManager.getInstance().playSoundFile(sound.getFile().getAbsolutePath(), sprite); } } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/actions/ReadVariableFromFileAction.kt b/catroid/src/main/java/org/catrobat/catroid/content/actions/ReadVariableFromFileAction.kt new file mode 100644 index 00000000000..a0506201382 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/content/actions/ReadVariableFromFileAction.kt @@ -0,0 +1,76 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2018 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.content.actions + +import android.util.Log +import androidx.annotation.VisibleForTesting +import org.catrobat.catroid.common.Constants +import org.catrobat.catroid.content.Sprite +import org.catrobat.catroid.formulaeditor.Formula +import org.catrobat.catroid.formulaeditor.UserVariable +import org.catrobat.catroid.utils.Utils +import java.io.File +import java.io.IOException + +class ReadVariableFromFileAction : EventAction() { + var sprite: Sprite? = null + var formula: Formula? = null + var userVariable: UserVariable? = null + var deleteFile: Boolean = false + + override fun act(delta: Float): Boolean { + if (userVariable == null || formula == null) { + return true + } + + var fileName = Utils.sanitizeFileName(formula!!.interpretString(sprite)) + if (!fileName.endsWith(".txt")) { + fileName += ".txt" + } + + getFile(fileName)?.let { + val content = readFromFile(it) + userVariable!!.value = content.toDoubleOrNull() ?: content + if (deleteFile) { + it.delete() + } + } + return true + } + + @VisibleForTesting + fun getFile(fileName: String): File? { + val file = File(Constants.EXTERNAL_STORAGE_ROOT_EXPORT_DIRECTORY, fileName) + return if (file.exists()) file else null + } + + @VisibleForTesting + fun readFromFile(file: File): String { + return try { + file.readText() + } catch (e: IOException) { + Log.e(javaClass.simpleName, "Could not read variable value from storage.") + "0" + } + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/actions/RepeatUntilAction.kt b/catroid/src/main/java/org/catrobat/catroid/content/actions/RepeatUntilAction.kt new file mode 100644 index 00000000000..f2c160e01dc --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/content/actions/RepeatUntilAction.kt @@ -0,0 +1,94 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2018 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.content.actions + +import android.util.Log +import com.badlogic.gdx.scenes.scene2d.actions.RepeatAction +import org.catrobat.catroid.content.Sprite +import org.catrobat.catroid.formulaeditor.Formula +import org.catrobat.catroid.formulaeditor.InterpretationException + +class RepeatUntilAction : RepeatAction() { + var executedCount = 0 + private set + var sprite: Sprite? = null + var repeatCondition: Formula? = null + private var isCurrentLoopInitialized = false + private var currentTime = 0f + + private fun isValidConditionFormula(): Boolean { + try { + repeatCondition?.interpretDouble(sprite) ?: return false + } catch (interpretationException: InterpretationException) { + Log.d( + javaClass.simpleName, "Formula interpretation for this specific Brick failed.", + interpretationException + ) + return false + } + return true + } + + private fun isConditionTrue(): Boolean = try { + repeatCondition?.interpretDouble(sprite) != 0.0 + } catch (interpretationException: InterpretationException) { + Log.d( + javaClass.simpleName, "Formula interpretation for this specific Brick failed.", + interpretationException + ) + true + } + + public override fun delegate(delta: Float): Boolean { + if (!isValidConditionFormula()) { + return true + } + if (!isCurrentLoopInitialized) { + if (isConditionTrue()) { + return true + } + currentTime = 0f + isCurrentLoopInitialized = true + } + currentTime += delta + if (action.act(delta) && currentTime >= LOOP_DELAY) { + executedCount++ + if (isConditionTrue()) { + return true + } + isCurrentLoopInitialized = false + action?.restart() + } + return false + } + + override fun restart() { + isCurrentLoopInitialized = false + executedCount = 0 + super.restart() + } + + companion object { + private const val LOOP_DELAY = 0.02f + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/actions/SpeakAction.java b/catroid/src/main/java/org/catrobat/catroid/content/actions/SpeakAction.java index 8529abd2dfa..0989fa6dfbb 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/actions/SpeakAction.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/actions/SpeakAction.java @@ -92,7 +92,7 @@ public void onUtteranceCompleted(String utteranceId) { if (determineLength) { lengthOfText = SoundManager.getInstance().getDurationOfSoundFile(speechFile.getAbsolutePath()); } else { - SoundManager.getInstance().playSoundFile(speechFile.getAbsolutePath()); + SoundManager.getInstance().playSoundFile(speechFile.getAbsolutePath(), sprite); } } }; @@ -118,6 +118,10 @@ public float getLengthOfText() { return lengthOfText; } + public File getSpeechFile() { + return speechFile; + } + public void setDetermineLength(boolean getDurationOfText) { this.determineLength = getDurationOfText; } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/actions/StopSoundAction.kt b/catroid/src/main/java/org/catrobat/catroid/content/actions/StopSoundAction.kt new file mode 100644 index 00000000000..a5f8a762722 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/content/actions/StopSoundAction.kt @@ -0,0 +1,39 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2018 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.content.actions + +import com.badlogic.gdx.scenes.scene2d.actions.TemporalAction +import org.catrobat.catroid.common.SoundInfo +import org.catrobat.catroid.content.Sprite +import org.catrobat.catroid.io.SoundManager + +class StopSoundAction : TemporalAction() { + var sprite: Sprite? = null + var sound: SoundInfo? = null + + override fun update(percent: Float) { + sound?.file?.let { + SoundManager.getInstance().stopSameSoundInSprite(it.absolutePath, sprite) + } + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/actions/WaitAction.java b/catroid/src/main/java/org/catrobat/catroid/content/actions/WaitAction.java index e51cbd29e75..235cce3b75e 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/actions/WaitAction.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/actions/WaitAction.java @@ -33,7 +33,7 @@ public class WaitAction extends TemporalAction { protected Sprite sprite; - private Formula duration; + protected Formula duration; @Override protected void begin() { diff --git a/catroid/src/main/java/org/catrobat/catroid/content/actions/WaitForSoundAction.java b/catroid/src/main/java/org/catrobat/catroid/content/actions/WaitForSoundAction.java new file mode 100644 index 00000000000..94cb8fc9eda --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/content/actions/WaitForSoundAction.java @@ -0,0 +1,73 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2020 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.content.actions; + +import org.catrobat.catroid.content.MediaPlayerWithSoundDetails; +import org.catrobat.catroid.content.SoundFilePathWithSprite; +import org.catrobat.catroid.io.SoundManager; + +import java.util.Set; + +import androidx.annotation.VisibleForTesting; + +public class WaitForSoundAction extends WaitAction { + public static final String TAG = WaitForSoundAction.class.getSimpleName(); + private String soundFilePath; + private SoundManager soundManager = SoundManager.getInstance(); + private boolean soundStopped = false; + + public void setSoundFilePath(String soundFilePath) { + this.soundFilePath = soundFilePath; + } + + @Override + protected void update(float percent) { + if (soundFilePath != null && !soundManager.getRecentlyStoppedSoundfilePaths().isEmpty()) { + SoundFilePathWithSprite spriteSoundFilePath = + new SoundFilePathWithSprite(soundFilePath, sprite); + Set recentlyStopped = + soundManager.getRecentlyStoppedSoundfilePaths(); + if (recentlyStopped.contains(spriteSoundFilePath)) { + recentlyStopped.remove(spriteSoundFilePath); + finish(); + soundStopped = true; + } + } + } + + @Override + protected void end() { + for (MediaPlayerWithSoundDetails mediaPlayer : soundManager.getMediaPlayers()) { + if (mediaPlayer.isPlaying() && mediaPlayer.getStartedBySprite() == sprite && mediaPlayer.getPathToSoundFile().equals(soundFilePath) && !soundStopped) { + restart(); + setTime(mediaPlayer.getCurrentPosition()); + } + } + } + + @VisibleForTesting + public void setSoundManager(SoundManager soundManager) { + this.soundManager = soundManager; + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/actions/WebAction.kt b/catroid/src/main/java/org/catrobat/catroid/content/actions/WebAction.kt index 47d0d2dbc29..65e55e1dbf1 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/actions/WebAction.kt +++ b/catroid/src/main/java/org/catrobat/catroid/content/actions/WebAction.kt @@ -26,16 +26,16 @@ import android.util.Log import androidx.annotation.CallSuper import com.badlogic.gdx.scenes.scene2d.Action import okhttp3.Response -import org.catrobat.catroid.ProjectManager +import org.catrobat.catroid.TrustedDomainManager import org.catrobat.catroid.common.Constants import org.catrobat.catroid.content.Sprite import org.catrobat.catroid.formulaeditor.Formula import org.catrobat.catroid.formulaeditor.InterpretationException +import org.catrobat.catroid.stage.BrickDialogManager import org.catrobat.catroid.stage.StageActivity import org.catrobat.catroid.stage.StageActivity.stageListener import org.catrobat.catroid.web.WebConnection import org.catrobat.catroid.web.WebConnection.WebRequestListener -import java.util.ArrayList abstract class WebAction : Action(), WebRequestListener { private var webConnection: WebConnection? = null @@ -72,8 +72,8 @@ abstract class WebAction : Action(), WebRequestListener { denyPermission() } else { permissionStatus = PermissionStatus.PENDING - val params = ArrayList(listOf(this, url)) - StageActivity.messageHandler.obtainMessage(StageActivity.REQUEST_PERMISSION, params).sendToTarget() + val params = arrayListOf(BrickDialogManager.DialogType.WEB_ACCESS_DIALOG, this, url!!) + StageActivity.messageHandler.obtainMessage(StageActivity.SHOW_DIALOG, params).sendToTarget() } } @@ -117,7 +117,7 @@ abstract class WebAction : Action(), WebRequestListener { } private fun checkPermission() = - if (ProjectManager.checkIfURLIsWhitelisted(url)) { + if (TrustedDomainManager.isURLTrusted(url!!)) { grantPermission() } else { askForPermission() diff --git a/catroid/src/main/java/org/catrobat/catroid/content/actions/WriteVariableToFileAction.kt b/catroid/src/main/java/org/catrobat/catroid/content/actions/WriteVariableToFileAction.kt new file mode 100644 index 00000000000..bd0aef5ddac --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/content/actions/WriteVariableToFileAction.kt @@ -0,0 +1,80 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2018 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.content.actions + +import android.util.Log +import androidx.annotation.VisibleForTesting +import org.catrobat.catroid.CatroidApplication +import org.catrobat.catroid.R +import org.catrobat.catroid.common.Constants +import org.catrobat.catroid.content.Sprite +import org.catrobat.catroid.formulaeditor.Formula +import org.catrobat.catroid.formulaeditor.UserVariable +import org.catrobat.catroid.stage.StageActivity +import org.catrobat.catroid.utils.Utils +import java.io.File +import java.io.IOException +import java.util.ArrayList + +class WriteVariableToFileAction : EventAction() { + var sprite: Sprite? = null + var formula: Formula? = null + var userVariable: UserVariable? = null + + override fun act(delta: Float): Boolean { + if (userVariable == null || formula == null) { + return true + } + + var fileName = Utils.sanitizeFileName(formula!!.interpretString(sprite)) + if (!fileName.endsWith(".txt")) { + fileName += ".txt" + } + + createFile(fileName)?.let { + writeToFile(it, userVariable!!.value.toString()) + } + return true + } + + @VisibleForTesting + fun createFile(fileName: String): File? { + val file = File(Constants.EXTERNAL_STORAGE_ROOT_EXPORT_DIRECTORY, fileName) + return if (file.exists() || file.createNewFile()) { + file + } else null + } + + @VisibleForTesting + fun writeToFile(file: File, content: String) { + try { + file.writeText(content) + val context = CatroidApplication.getAppContext() + val message = context.getString(R.string.brick_write_variable_to_file_success, file) + val params = ArrayList(listOf(message)) + StageActivity.messageHandler.obtainMessage(StageActivity.SHOW_TOAST, params).sendToTarget() + } catch (e: IOException) { + Log.e(javaClass.simpleName, "Could not write variable value to storage.") + } + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/ArduinoSendPWMValueBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/ArduinoSendPWMValueBrick.java index 4cefe5c7571..bf58556ffd1 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/ArduinoSendPWMValueBrick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/ArduinoSendPWMValueBrick.java @@ -73,7 +73,7 @@ public BrickField getDefaultBrickField() { public void updateArduinoValues994to995() { Formula formula = getFormulaWithBrickField(BrickField.ARDUINO_ANALOG_PIN_VALUE); - FormulaElement oldFormulaElement = formula.getFormulaTree(); + FormulaElement oldFormulaElement = formula.getRoot(); FormulaElement multiplication = new FormulaElement(FormulaElement.ElementType.OPERATOR, Operators.MULT.toString(), null); diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/AssertUserListsBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/AssertUserListsBrick.java new file mode 100644 index 00000000000..4f1007ed082 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/AssertUserListsBrick.java @@ -0,0 +1,60 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2018 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.content.bricks; + +import org.catrobat.catroid.R; +import org.catrobat.catroid.content.Sprite; +import org.catrobat.catroid.content.actions.ScriptSequenceAction; + +public class AssertUserListsBrick extends UserDataBrick { + + private static final long serialVersionUID = 1L; + + public AssertUserListsBrick() { + addAllowedBrickData(BrickData.ASSERT_LISTS_ACTUAL, R.id.brick_assert_lists_actual); + addAllowedBrickData(BrickData.ASSERT_LISTS_EXPECTED, R.id.brick_assert_lists_expected); + } + + @Override + public int getViewResource() { + return R.layout.brick_assert_userlists; + } + + @Override + public void addActionToSequence(Sprite sprite, ScriptSequenceAction sequence) { + sequence.addAction(sprite.getActionFactory().createAssertUserListsAction(sprite, + getUserListWithBrickData(BrickData.ASSERT_LISTS_ACTUAL), + getUserListWithBrickData(BrickData.ASSERT_LISTS_EXPECTED), + getPositionInformation())); + } + + private String getPositionInformation() { + int position = 999; + String scriptName = "unknown"; + if (getParent() != null) { + position = getPositionInScript(); + scriptName = getScript().getClass().getSimpleName(); + } + return "Brick at position " + position + "\nin \"" + scriptName + "\""; + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/Brick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/Brick.java index 5eeb048d96c..42c70ef196c 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/Brick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/Brick.java @@ -44,13 +44,13 @@ enum BrickField { COLOR, COLOR_CHANGE, BRIGHTNESS, BRIGHTNESS_CHANGE, X_POSITION, Y_POSITION, X_POSITION_CHANGE, Y_POSITION_CHANGE, TRANSPARENCY, TRANSPARENCY_CHANGE, SIZE, SIZE_CHANGE, VOLUME, VOLUME_CHANGE, X_DESTINATION, Y_DESTINATION, STEPS, DURATION_IN_SECONDS, DEGREES, TURN_RIGHT_DEGREES, TURN_LEFT_DEGREES, TIME_TO_WAIT_IN_SECONDS, VARIABLE, - VARIABLE_CHANGE, WEB_REQUEST, LOOK_REQUEST, BACKGROUND_REQUEST, + VARIABLE_CHANGE, WEB_REQUEST, LOOK_REQUEST, BACKGROUND_REQUEST, WRITE_FILENAME, READ_FILENAME, PEN_SIZE, PEN_COLOR_RED, PEN_COLOR_GREEN, PEN_COLOR_BLUE, - IF_CONDITION, TIMES_TO_REPEAT, VIBRATE_DURATION_IN_SECONDS, USER_BRICK, NOTE, SPEAK, - SHOWTEXT, STRING, ROTATION_STYLE, REPEAT_UNTIL_CONDITION, ASK_QUESTION, NFC_NDEF_MESSAGE, ASK_SPEECH_QUESTION, - LOOK_INDEX, + IF_CONDITION, TIMES_TO_REPEAT, FOR_LOOP_FROM, FOR_LOOP_TO, VIBRATE_DURATION_IN_SECONDS, + USER_BRICK, NOTE, SPEAK, SHOWTEXT, STRING, ROTATION_STYLE, REPEAT_UNTIL_CONDITION, + ASK_QUESTION, NFC_NDEF_MESSAGE, ASK_SPEECH_QUESTION, LOOK_INDEX, LEGO_NXT_SPEED, LEGO_NXT_DEGREES, LEGO_NXT_FREQUENCY, LEGO_NXT_DURATION_IN_SECONDS, @@ -93,11 +93,27 @@ public static boolean isExpectingStringValue(BrickField field) { } } + enum BrickData { + ASSERT_LISTS_EXPECTED, ASSERT_LISTS_ACTUAL; + + public static final BrickData[] EXPECTS_USERLIST = {ASSERT_LISTS_EXPECTED, + ASSERT_LISTS_ACTUAL}; + + public static boolean isUserList(BrickData field) { + for (BrickData bf : EXPECTS_USERLIST) { + if (bf.equals(field)) { + return true; + } + } + return false; + } + } + @Retention(RetentionPolicy.SOURCE) @IntDef({TEXT_TO_SPEECH, BLUETOOTH_LEGO_NXT, PHYSICS, FACE_DETECTION, ARDRONE_SUPPORT, BLUETOOTH_SENSORS_ARDUINO, SOCKET_RASPI, CAMERA_FLASH, VIBRATION, BLUETOOTH_PHIRO, CAMERA_BACK, CAMERA_FRONT, SENSOR_ACCELERATION, SENSOR_INCLINATION, SENSOR_COMPASS, NFC_ADAPTER, VIDEO, SENSOR_GPS, COLLISION, - BLUETOOTH_LEGO_EV3, NETWORK_CONNECTION, CAST_REQUIRED, JUMPING_SUMO, MICROPHONE}) + BLUETOOTH_LEGO_EV3, NETWORK_CONNECTION, CAST_REQUIRED, JUMPING_SUMO, MICROPHONE, STORAGE_WRITE, STORAGE_READ}) @interface Resources { } @@ -125,6 +141,8 @@ public static boolean isExpectingStringValue(BrickField field) { int CAST_REQUIRED = 22; int JUMPING_SUMO = 23; int MICROPHONE = 24; + int STORAGE_READ = 25; + int STORAGE_WRITE = 26; class ResourcesSet extends HashSet { @Override diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/BroadcastMessageBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/BroadcastMessageBrick.java index 04ce510e45d..0db0d55de98 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/BroadcastMessageBrick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/BroadcastMessageBrick.java @@ -77,7 +77,7 @@ public View getView(Context context) { } @Override - public void onNewOptionSelected() { + public void onNewOptionSelected(Integer spinnerId) { final AppCompatActivity activity = UiUtils.getActivityFromView(view); if (!(activity instanceof SpriteActivity)) { return; @@ -101,12 +101,12 @@ public void addItem(String item) { } @Override - public void onStringOptionSelected(String string) { + public void onStringOptionSelected(Integer spinnerId, String string) { setBroadcastMessage(string); } @Override - public void onItemSelected(@Nullable StringOption item) { + public void onItemSelected(Integer spinnerId, @Nullable StringOption item) { } @VisibleForTesting diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/CloneBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/CloneBrick.java index c0a0410291d..f89bad23399 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/CloneBrick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/CloneBrick.java @@ -70,16 +70,16 @@ public View getView(Context context) { } @Override - public void onNewOptionSelected() { + public void onNewOptionSelected(Integer spinnerId) { } @Override - public void onStringOptionSelected(String string) { + public void onStringOptionSelected(Integer spinnerId, String string) { objectToClone = null; } @Override - public void onItemSelected(@Nullable Sprite item) { + public void onItemSelected(Integer spinnerId, @Nullable Sprite item) { objectToClone = item; } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/ForVariableFromToBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/ForVariableFromToBrick.java new file mode 100644 index 00000000000..cf37513489b --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/ForVariableFromToBrick.java @@ -0,0 +1,225 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2020 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.content.bricks; + +import com.badlogic.gdx.scenes.scene2d.Action; + +import org.catrobat.catroid.R; +import org.catrobat.catroid.common.Constants; +import org.catrobat.catroid.content.ActionFactory; +import org.catrobat.catroid.content.Sprite; +import org.catrobat.catroid.content.actions.ScriptSequenceAction; +import org.catrobat.catroid.formulaeditor.Formula; +import org.catrobat.catroid.formulaeditor.UserVariable; + +import java.util.ArrayList; +import java.util.List; + +public class ForVariableFromToBrick extends UserVariableBrickWithFormula implements CompositeBrick { + + private transient EndBrick endBrick = new EndBrick(this); + + private List loopBricks = new ArrayList<>(); + + public ForVariableFromToBrick() { + addAllowedBrickField(BrickField.FOR_LOOP_FROM, R.id.brick_loop_from_edit); + addAllowedBrickField(BrickField.FOR_LOOP_TO, R.id.brick_loop_to_edit); + } + + public ForVariableFromToBrick(int from, int to) { + this(new Formula(from), new Formula(to)); + } + + public ForVariableFromToBrick(Formula from, Formula to) { + this(); + setFormulaWithBrickField(BrickField.FOR_LOOP_FROM, from); + setFormulaWithBrickField(BrickField.FOR_LOOP_TO, to); + } + + @Override + protected int getSpinnerId() { + return R.id.brick_for_variable_spinner; + } + + @Override + public int getViewResource() { + return R.layout.brick_for_variable_from_to; + } + + @Override + public BrickField getDefaultBrickField() { + return BrickField.FOR_LOOP_FROM; + } + + @Override + public boolean hasSecondaryList() { + return false; + } + + @Override + public List getNestedBricks() { + return loopBricks; + } + + @Override + public List getSecondaryNestedBricks() { + return null; + } + + public boolean addBrick(Brick brick) { + return loopBricks.add(brick); + } + + @Override + public void setCommentedOut(boolean commentedOut) { + super.setCommentedOut(commentedOut); + for (Brick brick : loopBricks) { + brick.setCommentedOut(commentedOut); + } + endBrick.setCommentedOut(commentedOut); + } + + @Override + public Brick clone() throws CloneNotSupportedException { + ForVariableFromToBrick clone = (ForVariableFromToBrick) super.clone(); + clone.endBrick = new EndBrick(clone); + clone.loopBricks = new ArrayList<>(); + for (Brick brick : loopBricks) { + clone.addBrick(brick.clone()); + } + return clone; + } + + @Override + public boolean consistsOfMultipleParts() { + return true; + } + + @Override + public List getAllParts() { + List bricks = new ArrayList<>(); + bricks.add(this); + bricks.add(endBrick); + return bricks; + } + + @Override + public void addToFlatList(List bricks) { + super.addToFlatList(bricks); + for (Brick brick : loopBricks) { + brick.addToFlatList(bricks); + } + bricks.add(endBrick); + } + + @Override + public void setParent(Brick parent) { + super.setParent(parent); + for (Brick brick : loopBricks) { + brick.setParent(this); + } + } + + @Override + public List getDragAndDropTargetList() { + return loopBricks; + } + + @Override + public boolean removeChild(Brick brick) { + if (loopBricks.remove(brick)) { + return true; + } + for (Brick childBrick : loopBricks) { + if (childBrick.removeChild(brick)) { + return true; + } + } + return false; + } + + @Override + public void addActionToSequence(Sprite sprite, ScriptSequenceAction sequence) { + if (userVariable == null || userVariable.getName() == null) { + userVariable = new UserVariable("NoVariableSet", Constants.NO_VARIABLE_SELECTED); + userVariable.setDummy(true); + } + + ScriptSequenceAction repeatSequence = (ScriptSequenceAction) ActionFactory.eventSequence(sequence.getScript()); + + for (Brick brick : loopBricks) { + if (!brick.isCommentedOut()) { + brick.addActionToSequence(sprite, repeatSequence); + } + } + + Action action = sprite.getActionFactory() + .createForVariableFromToAction(sprite, userVariable, + getFormulaWithBrickField(BrickField.FOR_LOOP_FROM), + getFormulaWithBrickField(BrickField.FOR_LOOP_TO), repeatSequence); + + sequence.addAction(action); + } + + private static class EndBrick extends BrickBaseType { + + EndBrick(ForVariableFromToBrick parent) { + this.parent = parent; + } + + @Override + public boolean consistsOfMultipleParts() { + return true; + } + + @Override + public List getAllParts() { + return parent.getAllParts(); + } + + @Override + public void addToFlatList(List bricks) { + parent.addToFlatList(bricks); + } + + @Override + public List getDragAndDropTargetList() { + return parent.getParent().getDragAndDropTargetList(); + } + + @Override + public int getPositionInDragAndDropTargetList() { + return parent.getParent().getDragAndDropTargetList().indexOf(parent); + } + + @Override + public int getViewResource() { + return R.layout.brick_loop_end; + } + + @Override + public void addActionToSequence(Sprite sprite, ScriptSequenceAction sequence) { + } + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/GoToBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/GoToBrick.java index 2fbeb5d291c..350e2c8020a 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/GoToBrick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/GoToBrick.java @@ -85,11 +85,11 @@ public View getView(Context context) { } @Override - public void onNewOptionSelected() { + public void onNewOptionSelected(Integer spinnerId) { } @Override - public void onStringOptionSelected(String string) { + public void onStringOptionSelected(Integer spinnerId, String string) { Context context = view.getContext(); if (string.equals(context.getString(R.string.brick_go_to_touch_position))) { @@ -102,7 +102,7 @@ public void onStringOptionSelected(String string) { } @Override - public void onItemSelected(@Nullable Sprite item) { + public void onItemSelected(Integer spinnerId, @Nullable Sprite item) { spinnerSelection = BrickValues.GO_TO_OTHER_SPRITE_POSITION; destinationSprite = item; } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/PlaySoundAndWaitBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/PlaySoundAndWaitBrick.java index a4560013c56..cecfb9af54d 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/PlaySoundAndWaitBrick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/PlaySoundAndWaitBrick.java @@ -66,6 +66,7 @@ public void addActionToSequence(Sprite sprite, ScriptSequenceAction sequence) { } } - sequence.addAction(sprite.getActionFactory().createWaitAction(sprite, new Formula(duration))); + sequence.addAction(sprite.getActionFactory().createWaitForSoundAction(sprite, + new Formula(duration), sound.getFile().getAbsolutePath())); } } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/PlaySoundBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/PlaySoundBrick.java index 033d29aa377..8f017f0d9dd 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/PlaySoundBrick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/PlaySoundBrick.java @@ -94,7 +94,7 @@ protected void onViewCreated(View view) { } @Override - public void onNewOptionSelected() { + public void onNewOptionSelected(Integer spinnerId) { AppCompatActivity activity = UiUtils.getActivityFromView(view); if (!(activity instanceof SpriteActivity)) { return; @@ -110,11 +110,11 @@ public void addItem(SoundInfo item) { } @Override - public void onStringOptionSelected(String string) { + public void onStringOptionSelected(Integer spinnerId, String string) { } @Override - public void onItemSelected(@Nullable SoundInfo item) { + public void onItemSelected(Integer spinnerId, @Nullable SoundInfo item) { sound = item; } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/PointToBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/PointToBrick.java index 5284cceb5b7..115ca340bd4 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/PointToBrick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/PointToBrick.java @@ -88,7 +88,7 @@ public View getView(Context context) { } @Override - public void onNewOptionSelected() { + public void onNewOptionSelected(Integer spinnerId) { AppCompatActivity activity = UiUtils.getActivityFromView(view); if (!(activity instanceof SpriteActivity)) { return; @@ -104,11 +104,11 @@ public void addItem(Sprite item) { } @Override - public void onStringOptionSelected(String string) { + public void onStringOptionSelected(Integer spinnerId, String string) { } @Override - public void onItemSelected(@Nullable Sprite item) { + public void onItemSelected(Integer spinnerId, @Nullable Sprite item) { pointedObject = item; } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/ReadVariableFromFileBrick.kt b/catroid/src/main/java/org/catrobat/catroid/content/bricks/ReadVariableFromFileBrick.kt new file mode 100644 index 00000000000..3ed819a2594 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/ReadVariableFromFileBrick.kt @@ -0,0 +1,102 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2018 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.content.bricks + +import android.content.Context +import android.view.View +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.Spinner +import org.catrobat.catroid.R +import org.catrobat.catroid.content.Sprite +import org.catrobat.catroid.content.actions.ScriptSequenceAction +import org.catrobat.catroid.content.bricks.Brick.BrickField +import org.catrobat.catroid.content.bricks.Brick.ResourcesSet +import org.catrobat.catroid.formulaeditor.Formula + +class ReadVariableFromFileBrick constructor() : UserVariableBrickWithFormula() { + constructor(value: String) : this(Formula(value)) + + constructor(formula: Formula) : this() { + setFormulaWithBrickField(BrickField.READ_FILENAME, formula) + } + + init { + addAllowedBrickField(BrickField.READ_FILENAME, R.id.brick_read_variable_from_file_edit_text) + } + + private var spinnerSelectionID: Int = KEEP + + companion object Mode { + private const val KEEP = 0 + private const val DELETE = 1 + } + + override fun getViewResource(): Int = R.layout.brick_read_variable_from_file + + override fun getSpinnerId(): Int = R.id.brick_read_variable_from_file_spinner_variable + + override fun getView(context: Context): View { + super.getView(context) + view.findViewById(R.id.brick_read_variable_from_file_spinner_mode).apply { + adapter = createArrayAdapter(context) + onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(adapterView: AdapterView<*>, view: View, position: Int, l: Long) { + spinnerSelectionID = position + } + override fun onNothingSelected(adapterView: AdapterView<*>) = Unit + } + setSelection(spinnerSelectionID) + } + return view + } + + private fun createArrayAdapter(context: Context): ArrayAdapter { + val spinnerValues = arrayOfNulls(2) + spinnerValues[KEEP] = context.getString(R.string.brick_read_variable_from_file_keep) + spinnerValues[DELETE] = context.getString(R.string.brick_read_variable_from_file_delete) + + ArrayAdapter(context, android.R.layout.simple_spinner_item, spinnerValues).apply { + setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + return this + } + } + + override fun addActionToSequence(sprite: Sprite, sequence: ScriptSequenceAction) { + userVariable?.name?.let { + sequence.addAction( + sprite.actionFactory.createReadVariableFromFileAction( + sprite, + getFormulaWithBrickField(BrickField.READ_FILENAME), + userVariable, + spinnerSelectionID == DELETE + ) + ) + } + } + + override fun addRequiredResources(requiredResourcesSet: ResourcesSet) { + requiredResourcesSet.add(STORAGE_READ) + super.addRequiredResources(requiredResourcesSet) + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/SceneStartBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/SceneStartBrick.java index 03f402039e5..8cb46d909be 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/SceneStartBrick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/SceneStartBrick.java @@ -96,7 +96,7 @@ public View getView(final Context context) { } @Override - public void onNewOptionSelected() { + public void onNewOptionSelected(Integer spinnerId) { AppCompatActivity activity = UiUtils.getActivityFromView(view); if (activity == null) { return; @@ -129,11 +129,11 @@ public void onNewOptionSelected() { } @Override - public void onStringOptionSelected(String string) { + public void onStringOptionSelected(Integer spinnerId, String string) { } @Override - public void onItemSelected(@Nullable Scene item) { + public void onItemSelected(Integer spinnerId, @Nullable Scene item) { sceneToStart = item != null ? item.getName() : null; } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/SceneTransitionBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/SceneTransitionBrick.java index 4071e8c565e..767513a5143 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/SceneTransitionBrick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/SceneTransitionBrick.java @@ -97,7 +97,7 @@ public View getView(final Context context) { } @Override - public void onNewOptionSelected() { + public void onNewOptionSelected(Integer spinnerId) { AppCompatActivity activity = UiUtils.getActivityFromView(view); if (activity == null) { return; @@ -130,11 +130,11 @@ public void onNewOptionSelected() { } @Override - public void onStringOptionSelected(String string) { + public void onStringOptionSelected(Integer spinnerId, String string) { } @Override - public void onItemSelected(@Nullable Scene item) { + public void onItemSelected(Integer spinnerId, @Nullable Scene item) { sceneForTransition = item != null ? item.getName() : null; } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/SetBackgroundBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/SetBackgroundBrick.java index ee1b82ecd2c..90d9292313e 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/SetBackgroundBrick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/SetBackgroundBrick.java @@ -96,7 +96,7 @@ public void addActionToSequence(Sprite sprite, ScriptSequenceAction sequence) { } @Override - public void onNewOptionSelected() { + public void onNewOptionSelected(Integer spinnerId) { AppCompatActivity activity = UiUtils.getActivityFromView(view); if (!(activity instanceof SpriteActivity)) { return; @@ -112,11 +112,11 @@ public void addItem(LookData item) { } @Override - public void onStringOptionSelected(String string) { + public void onStringOptionSelected(Integer spinnerId, String string) { } @Override - public void onItemSelected(@Nullable LookData item) { + public void onItemSelected(Integer spinnerId, @Nullable LookData item) { look = item; } } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/SetLookBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/SetLookBrick.java index ab2997893c9..ede5689cf09 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/SetLookBrick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/SetLookBrick.java @@ -90,7 +90,7 @@ public View getView(Context context) { } @Override - public void onNewOptionSelected() { + public void onNewOptionSelected(Integer spinnerId) { AppCompatActivity activity = UiUtils.getActivityFromView(view); if (!(activity instanceof SpriteActivity)) { return; @@ -106,11 +106,11 @@ public void addItem(LookData item) { } @Override - public void onStringOptionSelected(String string) { + public void onStringOptionSelected(Integer spinnerId, String string) { } @Override - public void onItemSelected(@Nullable LookData item) { + public void onItemSelected(Integer spinnerId, @Nullable LookData item) { look = item; } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/SetNfcTagBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/SetNfcTagBrick.java index 8fdf78ed566..bb7908e8ea4 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/SetNfcTagBrick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/SetNfcTagBrick.java @@ -86,15 +86,15 @@ public View getView(Context context) { } @Override - public void onNewOptionSelected() { + public void onNewOptionSelected(Integer spinnerId) { } @Override - public void onStringOptionSelected(String string) { + public void onStringOptionSelected(Integer spinnerId, String string) { } @Override - public void onItemSelected(@Nullable NfcTypeOption item) { + public void onItemSelected(Integer spinnerId, @Nullable NfcTypeOption item) { nfcTagNdefType = item.getNfcTagNdefType(); } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/SetRotationStyleBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/SetRotationStyleBrick.java index dac84712a03..01b0e78dae8 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/SetRotationStyleBrick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/SetRotationStyleBrick.java @@ -80,15 +80,15 @@ public void addActionToSequence(Sprite sprite, ScriptSequenceAction sequence) { } @Override - public void onNewOptionSelected() { + public void onNewOptionSelected(Integer spinnerId) { } @Override - public void onStringOptionSelected(String string) { + public void onStringOptionSelected(Integer spinnerId, String string) { } @Override - public void onItemSelected(@Nullable RotationStyleOption item) { + public void onItemSelected(Integer spinnerId, @Nullable RotationStyleOption item) { selection = item != null ? item.getRotationStyle() : 0; } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/ShowTextColorSizeAlignmentBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/ShowTextColorSizeAlignmentBrick.java index 1acaf32cc8a..b51c217fb8a 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/ShowTextColorSizeAlignmentBrick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/ShowTextColorSizeAlignmentBrick.java @@ -35,8 +35,10 @@ import org.catrobat.catroid.content.strategy.ShowColorPickerFormulaEditorStrategy; import org.catrobat.catroid.content.strategy.ShowFormulaEditorStrategy; import org.catrobat.catroid.formulaeditor.Formula; +import org.catrobat.catroid.formulaeditor.FormulaElement; import org.catrobat.catroid.formulaeditor.FormulaElement.ElementType; import org.catrobat.catroid.formulaeditor.UserVariable; +import org.catrobat.catroid.formulaeditor.common.Conversions; import org.catrobat.catroid.ui.UiUtils; import java.util.ArrayList; @@ -122,18 +124,18 @@ public View getView(Context context) { spinner.setOnItemSelectedListener(new BrickSpinner.OnItemSelectedListener() { @Override - public void onStringOptionSelected(String string) { + public void onStringOptionSelected(Integer spinnerId, String string) { } @Override - public void onItemSelected(@Nullable AlignmentStyle item) { + public void onItemSelected(Integer spinnerId, @Nullable AlignmentStyle item) { if (item != null) { alignmentSelection = item.alignmentStyle; } } @Override - public void onNewOptionSelected() { + public void onNewOptionSelected(Integer spinnerId) { } }); return view; @@ -203,20 +205,20 @@ public int getValue() { if (!isColorBrickFieldAString()) { return Color.BLACK; } - String formulaString = getColorBrickFieldStringValue(); - if (formulaString.length() == 7 && formulaString.matches("^#[0-9a-fA-F]+$")) { - return Color.parseColor(formulaString); - } else { - return Color.BLACK; - } + String stringValue = getColorBrickFieldStringValue(); + return Conversions.tryParseColor(stringValue); } private boolean isColorBrickFieldAString() { - return getFormulaWithBrickField(BrickField.COLOR).getRoot().getElementType() == ElementType.STRING; + return getColorFormulaElement().getElementType() == ElementType.STRING; } private String getColorBrickFieldStringValue() { - return getFormulaWithBrickField(BrickField.COLOR).getRoot().getValue(); + return getColorFormulaElement().getValue(); + } + + private FormulaElement getColorFormulaElement() { + return getFormulaWithBrickField(BrickField.COLOR).getRoot(); } } } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/SpeakAndWaitBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/SpeakAndWaitBrick.java index 87c2d2cc4ab..349aafeb19f 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/SpeakAndWaitBrick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/SpeakAndWaitBrick.java @@ -29,10 +29,14 @@ import org.catrobat.catroid.content.actions.SpeakAction; import org.catrobat.catroid.formulaeditor.Formula; +import java.io.File; + public class SpeakAndWaitBrick extends FormulaBrick { private static final long serialVersionUID = 1L; + private File speechFile = null; + public SpeakAndWaitBrick() { addAllowedBrickField(BrickField.SPEAK, R.id.brick_speak_and_wait_edit_text); } @@ -61,9 +65,10 @@ public int getViewResource() { public void addActionToSequence(Sprite sprite, ScriptSequenceAction sequence) { sequence.addAction(sprite.getActionFactory() .createSpeakAction(sprite, getFormulaWithBrickField(BrickField.SPEAK))); - sequence.addAction(sprite.getActionFactory() - .createWaitAction(sprite, - new Formula(getDurationOfSpokenText(sprite, getFormulaWithBrickField(BrickField.SPEAK))))); + + sequence.addAction(sprite.getActionFactory().createWaitForSoundAction(sprite, + new Formula(getDurationOfSpokenText(sprite, + getFormulaWithBrickField(BrickField.SPEAK))), speechFile.getAbsolutePath())); } private float getDurationOfSpokenText(Sprite sprite, Formula text) { @@ -75,7 +80,7 @@ private float getDurationOfSpokenText(Sprite sprite, Formula text) { action.setDetermineLength(true); action.act(1.0f); - + speechFile = action.getSpeechFile(); return action.getLengthOfText() / 1000; } } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/StopSoundBrick.kt b/catroid/src/main/java/org/catrobat/catroid/content/bricks/StopSoundBrick.kt new file mode 100644 index 00000000000..f4ec64d2332 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/StopSoundBrick.kt @@ -0,0 +1,80 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2018 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.content.bricks + +import android.content.Context +import android.view.View +import org.catrobat.catroid.ProjectManager +import org.catrobat.catroid.R +import org.catrobat.catroid.common.Nameable +import org.catrobat.catroid.common.SoundInfo +import org.catrobat.catroid.content.Sprite +import org.catrobat.catroid.content.actions.ScriptSequenceAction +import org.catrobat.catroid.content.bricks.brickspinner.BrickSpinner +import org.catrobat.catroid.content.bricks.brickspinner.NewOption +import org.catrobat.catroid.ui.SpriteActivity +import org.catrobat.catroid.ui.UiUtils +import org.catrobat.catroid.ui.recyclerview.dialog.dialoginterface.NewItemInterface + +class StopSoundBrick : BrickBaseType(), + BrickSpinner.OnItemSelectedListener, NewItemInterface { + + var sound: SoundInfo? = null + lateinit var spinner: BrickSpinner + + override fun getViewResource() = R.layout.brick_stop_sound + + override fun getView(context: Context): View { + super.getView(context) + val items = mutableListOf(NewOption(context.getString(R.string.new_option))) + items.addAll(ProjectManager.getInstance().currentSprite.soundList) + spinner = BrickSpinner(R.id.brick_stop_sound_spinner, view, items) + spinner.apply { + setOnItemSelectedListener(this@StopSoundBrick) + setSelection(sound) + } + return view + } + + override fun onNewOptionSelected(spinnerId: Int) { + (UiUtils.getActivityFromView(view) as? SpriteActivity)?.apply { + registerOnNewSoundListener(this@StopSoundBrick) + handleAddSoundButton() + } + } + + override fun addItem(item: SoundInfo?) { + item?.let { spinner.add(it) } + spinner.setSelection(item) + } + + override fun onStringOptionSelected(spinnerId: Int, string: String) = Unit + + override fun onItemSelected(spinnerId: Int, item: SoundInfo?) { + sound = item + } + + override fun addActionToSequence(sprite: Sprite, sequence: ScriptSequenceAction) { + sequence.addAction(sprite.actionFactory.createStopSoundAction(sprite, sound)) + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserDataBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserDataBrick.java new file mode 100644 index 00000000000..4ee47a911c3 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserDataBrick.java @@ -0,0 +1,238 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2018 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.content.bricks; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.view.View; +import android.widget.RadioButton; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; + +import org.catrobat.catroid.ProjectManager; +import org.catrobat.catroid.R; +import org.catrobat.catroid.common.Nameable; +import org.catrobat.catroid.content.Project; +import org.catrobat.catroid.content.Sprite; +import org.catrobat.catroid.content.bricks.brickspinner.BrickSpinner; +import org.catrobat.catroid.content.bricks.brickspinner.NewOption; +import org.catrobat.catroid.formulaeditor.UserData; +import org.catrobat.catroid.formulaeditor.UserList; +import org.catrobat.catroid.formulaeditor.UserVariable; +import org.catrobat.catroid.ui.UiUtils; +import org.catrobat.catroid.ui.recyclerview.dialog.TextInputDialog; +import org.catrobat.catroid.ui.recyclerview.dialog.textwatcher.NewItemTextWatcher; +import org.catrobat.catroid.ui.recyclerview.fragment.ScriptFragment; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +public abstract class UserDataBrick extends FormulaBrick implements BrickSpinner.OnItemSelectedListener { + + public transient BiMap brickDataToTextViewIdMap = HashBiMap.create(2); + + protected UserDataHashMap userDataList = new UserDataHashMap(); + + private transient HashMap> spinnerMap = new HashMap<>(); + + @Override + public Brick clone() throws CloneNotSupportedException { + UserDataBrick clone = (UserDataBrick) super.clone(); + clone.userDataList = userDataList.clone(); + clone.spinnerMap = new HashMap<>(); + return clone; + } + + public UserList getUserListWithBrickData(BrickData brickData) { + if (userDataList.containsKey(brickData)) { + UserData result = userDataList.get(brickData); + if (result instanceof UserList) { + return (UserList) result; + } else { + return null; + } + } else { + throw new IllegalArgumentException("Incompatible Brick data: " + this.getClass().getSimpleName() + + " does not have BrickField." + brickData.toString()); + } + } + + public UserVariable getUserVariableWithBrickData(BrickData brickData) { + if (userDataList.containsKey(brickData)) { + UserData result = userDataList.get(brickData); + if (result instanceof UserVariable) { + return (UserVariable) result; + } else { + return null; + } + } else { + throw new IllegalArgumentException("Incompatible Brick data: " + this.getClass().getSimpleName() + + " does not have BrickField." + brickData.toString()); + } + } + + public HashMap getUserDataMap() { + return userDataList; + } + + protected void addAllowedBrickData(BrickData brickData, int textViewResourceId) { + if (!userDataList.containsKey(brickData)) { + userDataList.put(brickData, null); + } + brickDataToTextViewIdMap.put(brickData, textViewResourceId); + } + + public Brick.BrickData getBrickDataFromTextViewId(int textViewId) { + return brickDataToTextViewIdMap.inverse().get(textViewId); + } + + @Override + public View getView(Context context) { + super.getView(context); + + Sprite sprite = ProjectManager.getInstance().getCurrentSprite(); + + List lists = new ArrayList<>(); + lists.add(new NewOption(context.getString(R.string.new_option))); + lists.addAll(sprite.getUserLists()); + lists.addAll(ProjectManager.getInstance().getCurrentProject().getUserLists()); + + List variables = new ArrayList<>(); + variables.add(new NewOption(context.getString(R.string.new_option))); + variables.addAll(sprite.getUserVariables()); + variables.addAll(ProjectManager.getInstance().getCurrentProject().getUserVariables()); + + for (Map.Entry entry : userDataList.entrySet()) { + Integer spinnerid = brickDataToTextViewIdMap.get(entry.getKey()); + BrickSpinner spinner; + + if (Brick.BrickData.isUserList(entry.getKey())) { + spinner = new BrickSpinner<>(spinnerid, view, lists); + } else { + spinner = new BrickSpinner<>(spinnerid, view, variables); + } + + spinner.setOnItemSelectedListener(this); + spinner.setSelection(entry.getValue()); + spinnerMap.put(entry.getKey(), spinner); + } + + return view; + } + + @Override + public void onNewOptionSelected(Integer spinnerId) { + final AppCompatActivity activity = UiUtils.getActivityFromView(view); + if (activity == null) { + return; + } + + final Project currentProject = ProjectManager.getInstance().getCurrentProject(); + final Sprite currentSprite = ProjectManager.getInstance().getCurrentSprite(); + BrickData brickData = getBrickDataFromTextViewId(spinnerId); + + TextInputDialog.Builder builder = new TextInputDialog.Builder(activity); + + builder.setHint(activity.getString(R.string.data_label)) + .setTextWatcher(new NewItemTextWatcher<>(spinnerMap.get(brickData).getItems())) + .setPositiveButton(activity.getString(R.string.ok), new TextInputDialog.OnClickListener() { + @Override + public void onPositiveButtonClick(DialogInterface dialog, String textInput) { + + RadioButton addToProjectListsRadioButton = ((Dialog) dialog).findViewById(R.id.global); + boolean addToProjectData = addToProjectListsRadioButton.isChecked(); + boolean isUserList = BrickData.isUserList(brickData); + UserData userData; + if (isUserList) { + userData = new UserList(textInput); + if (addToProjectData) { + currentProject.addUserList((UserList) userData); + } else { + currentSprite.addUserList((UserList) userData); + } + } else { + userData = new UserVariable(textInput); + if (addToProjectData) { + currentProject.addUserVariable((UserVariable) userData); + } else { + currentSprite.addUserVariable((UserVariable) userData); + } + } + + for (Map.Entry> entry + : spinnerMap.entrySet()) { + if (BrickData.isUserList(entry.getKey()) == isUserList) { + entry.getValue().add(userData); + } + } + + spinnerMap.get(brickData).setSelection(userData); + + ScriptFragment parentFragment = (ScriptFragment) activity + .getSupportFragmentManager().findFragmentByTag(ScriptFragment.TAG); + if (parentFragment != null) { + parentFragment.notifyDataSetChanged(); + } + } + }); + + int title; + if (Brick.BrickData.isUserList(getBrickDataFromTextViewId(spinnerId))) { + title = R.string.formula_editor_list_dialog_title; + } else { + title = R.string.formula_editor_variable_dialog_title; + } + builder.setTitle(title) + .setView(R.layout.dialog_new_user_data) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + spinnerMap.get(brickData).setSelection(userDataList.get(brickData)); + } + }) + .setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + spinnerMap.get(brickData).setSelection(userDataList.get(brickData)); + } + }) + .show(); + } + + @Override + public void onStringOptionSelected(Integer spinnerId, String string) { + } + + @Override + public void onItemSelected(Integer spinnerId, @Nullable UserData item) { + userDataList.put(getBrickDataFromTextViewId(spinnerId), item); + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserDataHashMap.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserDataHashMap.java new file mode 100644 index 00000000000..f7f51c17b92 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserDataHashMap.java @@ -0,0 +1,55 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2018 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.content.bricks; + +import org.catrobat.catroid.formulaeditor.UserData; +import org.catrobat.catroid.formulaeditor.UserList; +import org.catrobat.catroid.formulaeditor.UserVariable; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; + +public class UserDataHashMap extends HashMap implements Cloneable { + + private static final long serialVersionUID = 9030965461744658052L; + + @NotNull + @Override + public UserDataHashMap clone() { + UserDataHashMap copiedMap = new UserDataHashMap(); + for (Map.Entry entry : entrySet()) { + UserData userData = null; + if (entry.getValue() != null) { + if (Brick.BrickData.isUserList(entry.getKey())) { + userData = new UserList((UserList) entry.getValue()); + } else { + userData = new UserVariable((UserVariable) entry.getValue()); + } + } + + copiedMap.put(entry.getKey(), userData); + } + return copiedMap; + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserDefinedBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserDefinedBrick.java index 5c68eaa6cd1..7d6ede91a38 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserDefinedBrick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserDefinedBrick.java @@ -28,22 +28,22 @@ import android.view.View; import android.widget.TextView; +import com.thoughtworks.xstream.annotations.XStreamAlias; + import org.catrobat.catroid.R; -import org.catrobat.catroid.common.Nameable; import org.catrobat.catroid.content.Sprite; import org.catrobat.catroid.content.actions.ScriptSequenceAction; -import org.catrobat.catroid.content.bricks.brickspinner.StringOption; import org.catrobat.catroid.ui.BrickLayout; -import org.catrobat.catroid.ui.fragment.AddUserDataToUserBrickFragment; +import org.catrobat.catroid.ui.fragment.AddUserDataToUserDefinedBrickFragment; import org.catrobat.catroid.ui.recyclerview.util.UniqueNameProvider; -import org.catrobat.catroid.userbrick.UserBrickData; -import org.catrobat.catroid.userbrick.UserBrickInput; -import org.catrobat.catroid.userbrick.UserBrickLabel; +import org.catrobat.catroid.userbrick.UserDefinedBrickData; +import org.catrobat.catroid.userbrick.UserDefinedBrickInput; +import org.catrobat.catroid.userbrick.UserDefinedBrickLabel; import java.util.ArrayList; import java.util.List; +import java.util.UUID; -import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; @@ -55,49 +55,80 @@ public class UserDefinedBrick extends BrickBaseType { public static final boolean INPUT = true; public static final boolean LABEL = false; - private List userBrickDataList; - private BrickLayout userBrickContentLayout; - public TextView currentUserDataEditText; + @XStreamAlias("userDefinedBrickDataList") + private List userDefinedBrickDataList; + + @XStreamAlias("userDefinedBrickID") + private UUID userDefinedBrickID; + + private transient BrickLayout userDefinedBrickLayout; + public transient TextView currentUserDefinedDataTextView; public UserDefinedBrick() { - userBrickDataList = new ArrayList<>(); + userDefinedBrickDataList = new ArrayList<>(); + userDefinedBrickID = UUID.randomUUID(); + } + + public UserDefinedBrick(List userBrickDataList) { + this.userDefinedBrickDataList = userBrickDataList; + this.userDefinedBrickID = UUID.randomUUID(); } - public UserDefinedBrick(List userBrickDataList) { - this.userBrickDataList = userBrickDataList; + public UserDefinedBrick(UserDefinedBrick userDefinedBrick) { + copyUserDefinedDataList(userDefinedBrick); + this.userDefinedBrickID = UUID.randomUUID(); + } + + private void copyUserDefinedDataList(UserDefinedBrick userDefinedBrick) { + this.userDefinedBrickDataList = new ArrayList<>(); + for (UserDefinedBrickData data : userDefinedBrick.getUserDefinedBrickDataList()) { + if (data instanceof UserDefinedBrickInput) { + userDefinedBrickDataList.add(new UserDefinedBrickInput((UserDefinedBrickInput) data)); + } else { + userDefinedBrickDataList.add(new UserDefinedBrickLabel((UserDefinedBrickLabel) data)); + } + } } - public void addLabel(Nameable label) { + public UUID getUserDefinedBrickID() { + return userDefinedBrickID; + } + + public void addLabel(String label) { removeLastLabel(); - userBrickDataList.add(new UserBrickLabel(label)); + userDefinedBrickDataList.add(new UserDefinedBrickLabel(label)); } public void removeLastLabel() { if (lastContentIsLabel()) { - userBrickDataList.remove(userBrickDataList.size() - 1); + userDefinedBrickDataList.remove(userDefinedBrickDataList.size() - 1); } } - public void addInput(Nameable input) { - userBrickDataList.add(new UserBrickInput(input)); + public void addInput(String input) { + userDefinedBrickDataList.add(new UserDefinedBrickInput(input)); } public boolean isEmpty() { - return userBrickDataList.isEmpty(); + return userDefinedBrickDataList.isEmpty(); + } + + public List getUserDefinedBrickDataList() { + return this.userDefinedBrickDataList; } - public List getUserDataList(boolean inputOrLabel) { - List userDataList = new ArrayList<>(); - if (inputOrLabel) { - for (UserBrickData userBrickData : userBrickDataList) { - if (userBrickData instanceof UserBrickInput) { - userDataList.add(((UserBrickInput) userBrickData).getInput()); + public List getUserDataList(boolean isInput) { + List userDataList = new ArrayList<>(); + if (isInput) { + for (UserDefinedBrickData userDefinedBrickData : userDefinedBrickDataList) { + if (userDefinedBrickData instanceof UserDefinedBrickInput) { + userDataList.add(((UserDefinedBrickInput) userDefinedBrickData).getInput()); } } } else { - for (UserBrickData userBrickData : userBrickDataList) { - if (userBrickData instanceof UserBrickLabel) { - userDataList.add(((UserBrickLabel) userBrickData).getLabel()); + for (UserDefinedBrickData userDefinedBrickData : userDefinedBrickDataList) { + if (userDefinedBrickData instanceof UserDefinedBrickLabel) { + userDataList.add(((UserDefinedBrickLabel) userDefinedBrickData).getLabel()); } } } @@ -105,59 +136,87 @@ public List getUserDataList(boolean inputOrLabel) { } private boolean lastContentIsLabel() { - if (userBrickDataList.isEmpty()) { + if (userDefinedBrickDataList.isEmpty()) { return false; } - return userBrickDataList.get(userBrickDataList.size() - 1) instanceof UserBrickLabel; + return userDefinedBrickDataList.get(userDefinedBrickDataList.size() - 1) instanceof UserDefinedBrickLabel; + } + + public boolean isUserDefinedBrickDataEqual(UserDefinedBrick other) { + if (userDefinedBrickDataList.size() != other.userDefinedBrickDataList.size()) { + return false; + } + for (int dataIndex = 0; dataIndex < userDefinedBrickDataList.size(); dataIndex++) { + UserDefinedBrickData thisData = userDefinedBrickDataList.get(dataIndex); + UserDefinedBrickData otherData = other.userDefinedBrickDataList.get(dataIndex); + + if (!thisData.getClass().equals(otherData.getClass())) { + return false; + } + if (thisData instanceof UserDefinedBrickLabel && !thisData.equals(otherData)) { + return false; + } + } + return true; + } + + @Override + public Brick clone() throws CloneNotSupportedException { + UserDefinedBrick clone = (UserDefinedBrick) super.clone(); + clone.copyUserDefinedDataList(this); + clone.userDefinedBrickID = this.getUserDefinedBrickID(); + return clone; } @Override public View getView(Context context) { super.getView(context); - userBrickContentLayout = view.findViewById(R.id.brick_user_brick); + userDefinedBrickLayout = view.findViewById(R.id.brick_user_brick); boolean isAddInputFragment = false; boolean isAddLabelFragment = false; Fragment currentFragment = ((FragmentActivity) context).getSupportFragmentManager().findFragmentById(R.id.fragment_container); - if (currentFragment instanceof AddUserDataToUserBrickFragment) { - isAddInputFragment = ((AddUserDataToUserBrickFragment) currentFragment).isAddInput(); + if (currentFragment instanceof AddUserDataToUserDefinedBrickFragment) { + isAddInputFragment = ((AddUserDataToUserDefinedBrickFragment) currentFragment).isAddInput(); isAddLabelFragment = !isAddInputFragment; } - for (UserBrickData userBrickData : userBrickDataList) { - if (userBrickData instanceof UserBrickInput) { - addTextViewForUserData(context, ((UserBrickInput) userBrickData).getInput(), + for (UserDefinedBrickData userBrickData : userDefinedBrickDataList) { + if (userBrickData instanceof UserDefinedBrickInput) { + addTextViewForUserData(context, ((UserDefinedBrickInput) userBrickData).getInput(), INPUT); } - if (userBrickData instanceof UserBrickLabel) { - addTextViewForUserData(context, ((UserBrickLabel) userBrickData).getLabel(), + if (userBrickData instanceof UserDefinedBrickLabel) { + addTextViewForUserData(context, ((UserDefinedBrickLabel) userBrickData).getLabel(), LABEL); } } if (isAddInputFragment) { - String defaultText = new UniqueNameProvider().getUniqueNameInNameables(context.getResources().getString(R.string.brick_user_defined_default_input_name), getUserDataList(INPUT)); - addTextViewForUserData(context, new StringOption(defaultText), INPUT); + String defaultText = + new UniqueNameProvider().getUniqueName(context.getResources().getString(R.string.brick_user_defined_default_input_name), getUserDataList(INPUT)); + addTextViewForUserData(context, defaultText, INPUT); } if (isAddLabelFragment && !lastContentIsLabel()) { - String defaultText = new UniqueNameProvider().getUniqueNameInNameables(context.getResources().getString(R.string.brick_user_defined_default_label), getUserDataList(LABEL)); - addTextViewForUserData(context, new StringOption(defaultText), LABEL); + String defaultText = + new UniqueNameProvider().getUniqueName(context.getResources().getString(R.string.brick_user_defined_default_label), getUserDataList(LABEL)); + addTextViewForUserData(context, defaultText, LABEL); } return view; } - private void addTextViewForUserData(Context context, Nameable text, boolean isInputOrLabel) { + private void addTextViewForUserData(Context context, String text, boolean isInput) { TextView userDataTextView; - if (isInputOrLabel) { + if (isInput) { userDataTextView = new TextView(new ContextThemeWrapper(context, R.style.BrickEditText)); } else { userDataTextView = new TextView(new ContextThemeWrapper(context, R.style.BrickText)); } - userDataTextView.setText(text.getName()); - currentUserDataEditText = userDataTextView; - userBrickContentLayout.addView(userDataTextView); + userDataTextView.setText(text); + currentUserDefinedDataTextView = userDataTextView; + userDefinedBrickLayout.addView(userDataTextView); } @Override @@ -165,34 +224,6 @@ public int getViewResource() { return R.layout.brick_user_brick; } - @Override - public boolean equals(@Nullable Object obj) { - if (obj instanceof UserDefinedBrick) { - UserDefinedBrick other = (UserDefinedBrick) obj; - if (userBrickDataList.size() == other.userBrickDataList.size()) { - int dataIndex; - for (dataIndex = 0; dataIndex < userBrickDataList.size(); dataIndex++) { - if (userBrickDataList.get(dataIndex) instanceof UserBrickLabel - && other.userBrickDataList.get(dataIndex) instanceof UserBrickLabel) { - if (!userBrickDataList.get(dataIndex).equals(other.userBrickDataList.get(dataIndex))) { - return false; - } - } else if (!(userBrickDataList.get(dataIndex) instanceof UserBrickInput - && other.userBrickDataList.get(dataIndex) instanceof UserBrickInput)) { - return false; - } - } - return true; - } - } - return false; - } - - @Override - public int hashCode() { - return userBrickDataList.hashCode(); - } - @Override public void addActionToSequence(Sprite sprite, ScriptSequenceAction sequence) { } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserDefinedReceiverBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserDefinedReceiverBrick.java new file mode 100644 index 00000000000..b09ed210cbb --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserDefinedReceiverBrick.java @@ -0,0 +1,104 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2020 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.content.bricks; + +import android.content.Context; +import android.view.View; +import android.widget.LinearLayout; + +import org.catrobat.catroid.ProjectManager; +import org.catrobat.catroid.R; +import org.catrobat.catroid.content.Script; +import org.catrobat.catroid.content.Sprite; +import org.catrobat.catroid.content.UserDefinedScript; +import org.catrobat.catroid.content.actions.ScriptSequenceAction; + +import androidx.annotation.VisibleForTesting; + +public class UserDefinedReceiverBrick extends ScriptBrickBaseType { + + private static final long serialVersionUID = 1L; + + private UserDefinedScript userDefinedScript; + private LinearLayout userBrickSpace; + private Brick userDefinedBrick; + + public UserDefinedReceiverBrick(UserDefinedScript userDefinedScript) { + this.userDefinedScript = userDefinedScript; + } + + public UserDefinedReceiverBrick(UserDefinedBrick userDefinedBrick) { + userDefinedScript = new UserDefinedScript(userDefinedBrick.getUserDefinedBrickID()); + userDefinedScript.setScriptBrick(this); + this.userDefinedBrick = userDefinedBrick; + } + + @VisibleForTesting + public UserDefinedReceiverBrick() { + this.userDefinedScript = new UserDefinedScript(); + } + + public UserDefinedBrick getUserDefinedBrick() { + return (UserDefinedBrick) userDefinedBrick; + } + + @Override + public Script getScript() { + return userDefinedScript; + } + + @Override + public Brick clone() throws CloneNotSupportedException { + UserDefinedReceiverBrick clone = (UserDefinedReceiverBrick) super.clone(); + clone.userDefinedScript = (UserDefinedScript) userDefinedScript.clone(); + clone.userDefinedScript.setScriptBrick(clone); + if (clone.userDefinedBrick != null) { + clone.userDefinedBrick = this.userDefinedBrick.clone(); + } + return clone; + } + + @Override + public View getView(Context context) { + super.getView(context); + userBrickSpace = view.findViewById(R.id.user_brick_space); + if (userDefinedBrick == null) { + Sprite currentSprite = ProjectManager.getInstance().getCurrentSprite(); + userDefinedBrick = currentSprite.getUserDefinedBrickByID(userDefinedScript.getUserDefinedBrickID()); + } + if (userDefinedBrick != null) { + userBrickSpace.addView(userDefinedBrick.getView(context)); + } + return view; + } + + @Override + public int getViewResource() { + return R.layout.brick_user_defined_script; + } + + @Override + public void addActionToSequence(Sprite sprite, ScriptSequenceAction sequence) { + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserListBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserListBrick.java index 6467f708b50..8f069c2c4dd 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserListBrick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserListBrick.java @@ -89,7 +89,7 @@ public View getView(Context context) { } @Override - public void onNewOptionSelected() { + public void onNewOptionSelected(Integer spinnerId) { final AppCompatActivity activity = UiUtils.getActivityFromView(view); if (activity == null) { return; @@ -127,11 +127,11 @@ public void onNegativeButton() { } @Override - public void onStringOptionSelected(String string) { + public void onStringOptionSelected(Integer spinnerId, String string) { } @Override - public void onItemSelected(@Nullable UserList item) { + public void onItemSelected(Integer spinnerId, @Nullable UserList item) { userList = item; } } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserVariableBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserVariableBrick.java index 483603fc61f..b656ae6955a 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserVariableBrick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserVariableBrick.java @@ -89,7 +89,7 @@ public View getView(Context context) { } @Override - public void onNewOptionSelected() { + public void onNewOptionSelected(Integer spinnerId) { final AppCompatActivity activity = UiUtils.getActivityFromView(view); if (activity == null) { return; @@ -105,11 +105,11 @@ public void onNewOptionSelected() { } @Override - public void onStringOptionSelected(String string) { + public void onStringOptionSelected(Integer spinnerId, String string) { } @Override - public void onItemSelected(@Nullable UserVariable item) { + public void onItemSelected(Integer spinnerId, @Nullable UserVariable item) { userVariable = item; } } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserVariableBrickWithFormula.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserVariableBrickWithFormula.java index 81b635d4cb8..ffa39abf2cd 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserVariableBrickWithFormula.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/UserVariableBrickWithFormula.java @@ -89,7 +89,7 @@ public View getView(Context context) { } @Override - public void onNewOptionSelected() { + public void onNewOptionSelected(Integer spinnerId) { final AppCompatActivity activity = UiUtils.getActivityFromView(view); if (activity == null) { return; @@ -105,11 +105,11 @@ public void onNewOptionSelected() { } @Override - public void onStringOptionSelected(String string) { + public void onStringOptionSelected(Integer spinnerId, String string) { } @Override - public void onItemSelected(@Nullable UserVariable item) { + public void onItemSelected(Integer spinnerId, @Nullable UserVariable item) { userVariable = item; } } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/WhenBackgroundChangesBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/WhenBackgroundChangesBrick.java index dfbeb0c4485..8fe905acb2e 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/WhenBackgroundChangesBrick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/WhenBackgroundChangesBrick.java @@ -113,7 +113,7 @@ public View getView(final Context context) { } @Override - public void onNewOptionSelected() { + public void onNewOptionSelected(Integer spinnerId) { AppCompatActivity activity = UiUtils.getActivityFromView(view); if (!(activity instanceof SpriteActivity)) { return; @@ -123,11 +123,11 @@ public void onNewOptionSelected() { } @Override - public void onStringOptionSelected(String string) { + public void onStringOptionSelected(Integer spinnerId, String string) { } @Override - public void onItemSelected(@Nullable LookData item) { + public void onItemSelected(Integer spinnerId, @Nullable LookData item) { setLook(item); } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/WhenBounceOffBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/WhenBounceOffBrick.java index 416f7132260..9467e5062fe 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/WhenBounceOffBrick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/WhenBounceOffBrick.java @@ -92,16 +92,16 @@ public View getView(Context context) { } @Override - public void onNewOptionSelected() { + public void onNewOptionSelected(Integer spinnerId) { } @Override - public void onStringOptionSelected(String string) { + public void onStringOptionSelected(Integer spinnerId, String string) { script.setSpriteToBounceOffName(null); } @Override - public void onItemSelected(@Nullable Sprite item) { + public void onItemSelected(Integer spinnerId, @Nullable Sprite item) { script.setSpriteToBounceOffName(item != null ? item.getName() : null); } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/WhenGamepadButtonBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/WhenGamepadButtonBrick.java index 1ac80ccc635..03c6e2ffbec 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/WhenGamepadButtonBrick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/WhenGamepadButtonBrick.java @@ -88,16 +88,16 @@ public View getView(Context context) { } @Override - public void onNewOptionSelected() { + public void onNewOptionSelected(Integer spinnerId) { } @Override - public void onStringOptionSelected(String string) { + public void onStringOptionSelected(Integer spinnerId, String string) { script.setAction(string); } @Override - public void onItemSelected(@Nullable StringOption item) { + public void onItemSelected(Integer spinnerId, @Nullable StringOption item) { } @Override diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/WhenNfcBrick.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/WhenNfcBrick.java index 5b341828e9f..4f9e78d71a7 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/WhenNfcBrick.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/WhenNfcBrick.java @@ -94,18 +94,18 @@ public View getView(Context context) { } @Override - public void onNewOptionSelected() { + public void onNewOptionSelected(Integer spinnerId) { script.setNfcTag(null); } @Override - public void onStringOptionSelected(String string) { + public void onStringOptionSelected(Integer spinnerId, String string) { script.setMatchAll(true); script.setNfcTag(null); } @Override - public void onItemSelected(@Nullable NfcTagData item) { + public void onItemSelected(Integer spinnerId, @Nullable NfcTagData item) { script.setNfcTag(item); script.setMatchAll(false); } diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/WriteVariableToFileBrick.kt b/catroid/src/main/java/org/catrobat/catroid/content/bricks/WriteVariableToFileBrick.kt new file mode 100644 index 00000000000..7ffb2e74324 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/WriteVariableToFileBrick.kt @@ -0,0 +1,59 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2018 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.content.bricks + +import org.catrobat.catroid.R +import org.catrobat.catroid.content.Sprite +import org.catrobat.catroid.content.actions.ScriptSequenceAction +import org.catrobat.catroid.content.bricks.Brick.BrickField +import org.catrobat.catroid.content.bricks.Brick.ResourcesSet +import org.catrobat.catroid.formulaeditor.Formula + +class WriteVariableToFileBrick constructor() : UserVariableBrickWithFormula() { + constructor(value: String) : this(Formula(value)) + + constructor(formula: Formula) : this() { + setFormulaWithBrickField(BrickField.WRITE_FILENAME, formula) + } + + init { + addAllowedBrickField(BrickField.WRITE_FILENAME, R.id.brick_write_variable_to_file_edit_text) + } + + override fun getViewResource(): Int = R.layout.brick_write_variable_to_file + + override fun getSpinnerId(): Int = R.id.brick_write_variable_to_file_spinner + + override fun addActionToSequence(sprite: Sprite, sequence: ScriptSequenceAction) { + userVariable?.name?.let { + sequence.addAction( + sprite.actionFactory.createWriteVariableToFileAction( + sprite, getFormulaWithBrickField(BrickField.WRITE_FILENAME), userVariable)) + } + } + + override fun addRequiredResources(requiredResourcesSet: ResourcesSet) { + requiredResourcesSet.addAll(listOf(Brick.STORAGE_READ, Brick.STORAGE_WRITE)) + super.addRequiredResources(requiredResourcesSet) + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/content/bricks/brickspinner/BrickSpinner.java b/catroid/src/main/java/org/catrobat/catroid/content/bricks/brickspinner/BrickSpinner.java index aa04cc2ee0b..2d9379f378b 100644 --- a/catroid/src/main/java/org/catrobat/catroid/content/bricks/brickspinner/BrickSpinner.java +++ b/catroid/src/main/java/org/catrobat/catroid/content/bricks/brickspinner/BrickSpinner.java @@ -45,10 +45,12 @@ public class BrickSpinner implements AdapterView.OnItemSelec private Spinner spinner; private BrickSpinnerAdapter adapter; + private Integer spinnerid; private OnItemSelectedListener onItemSelectedListener; - public BrickSpinner(int spinnerId, @NonNull View parent, List items) { + public BrickSpinner(Integer spinnerId, @NonNull View parent, List items) { + spinnerid = spinnerId; adapter = new BrickSpinnerAdapter(parent.getContext(), android.R.layout.simple_spinner_item, items); spinner = parent.findViewById(spinnerId); spinner.setAdapter(adapter); @@ -73,10 +75,10 @@ public void onItemSelected(AdapterView parent, View view, int position, long } if (item.getClass().equals(StringOption.class)) { - onItemSelectedListener.onStringOptionSelected(item.getName()); + onItemSelectedListener.onStringOptionSelected(spinnerid, item.getName()); return; } - onItemSelectedListener.onItemSelected((T) item); + onItemSelectedListener.onItemSelected(spinnerid, (T) item); } @Override @@ -131,14 +133,14 @@ private int consolidateSpinnerSelection(int position) { private void onSelectionSet(Nameable selectedItem) { if (onItemSelectedListener != null) { if (selectedItem.getClass().equals(NewOption.class)) { - onItemSelectedListener.onItemSelected(null); + onItemSelectedListener.onItemSelected(spinnerid, null); return; } if (selectedItem.getClass().equals(StringOption.class)) { - onItemSelectedListener.onStringOptionSelected(selectedItem.getName()); + onItemSelectedListener.onStringOptionSelected(spinnerid, selectedItem.getName()); return; } - onItemSelectedListener.onItemSelected((T) selectedItem); + onItemSelectedListener.onItemSelected(spinnerid, (T) selectedItem); } } @@ -166,7 +168,7 @@ public View getDropDownView(int position, @Nullable View convertView, @NonNull V @Override public boolean onTouch(View v, MotionEvent event) { if (event.getActionIndex() == MotionEvent.ACTION_DOWN && item.getClass().equals(NewOption.class)) { - onItemSelectedListener.onNewOptionSelected(); + onItemSelectedListener.onNewOptionSelected(spinnerid); } return false; } @@ -213,10 +215,10 @@ boolean containsNewOption() { public interface OnItemSelectedListener { - void onNewOptionSelected(); + void onNewOptionSelected(Integer spinnerId); - void onStringOptionSelected(String string); + void onStringOptionSelected(Integer spinnerId, String string); - void onItemSelected(@Nullable T item); + void onItemSelected(Integer spinnerId, @Nullable T item); } } diff --git a/catroid/src/main/java/org/catrobat/catroid/devices/mindstorms/ev3/sensors/EV3Sensor.java b/catroid/src/main/java/org/catrobat/catroid/devices/mindstorms/ev3/sensors/EV3Sensor.java index 3938d2e47eb..c47e078e9b6 100644 --- a/catroid/src/main/java/org/catrobat/catroid/devices/mindstorms/ev3/sensors/EV3Sensor.java +++ b/catroid/src/main/java/org/catrobat/catroid/devices/mindstorms/ev3/sensors/EV3Sensor.java @@ -36,6 +36,7 @@ import org.catrobat.catroid.devices.mindstorms.ev3.EV3CommandByte.EV3CommandVariableScope; import org.catrobat.catroid.devices.mindstorms.ev3.EV3CommandType; import org.catrobat.catroid.devices.mindstorms.ev3.EV3Reply; +import org.catrobat.catroid.utils.EnumUtils; import java.util.Locale; @@ -71,15 +72,8 @@ public String getSensorCode() { } public static EV3Sensor.Sensor getSensorFromSensorCode(String sensorCode) { - if (sensorCode == null) { - return Sensor.NO_SENSOR; - } - - try { - return valueOf(sensorCode); - } catch (IllegalArgumentException e) { - return Sensor.NO_SENSOR; - } + Sensor sensor = EnumUtils.getEnum(Sensor.class, sensorCode); + return sensor != null ? sensor : Sensor.NO_SENSOR; } } diff --git a/catroid/src/main/java/org/catrobat/catroid/devices/mindstorms/nxt/sensors/NXTSensor.java b/catroid/src/main/java/org/catrobat/catroid/devices/mindstorms/nxt/sensors/NXTSensor.java index accbb0684b1..8999769313d 100644 --- a/catroid/src/main/java/org/catrobat/catroid/devices/mindstorms/nxt/sensors/NXTSensor.java +++ b/catroid/src/main/java/org/catrobat/catroid/devices/mindstorms/nxt/sensors/NXTSensor.java @@ -32,6 +32,7 @@ import org.catrobat.catroid.devices.mindstorms.nxt.CommandType; import org.catrobat.catroid.devices.mindstorms.nxt.NXTError; import org.catrobat.catroid.devices.mindstorms.nxt.NXTReply; +import org.catrobat.catroid.utils.EnumUtils; import java.util.Locale; @@ -62,15 +63,8 @@ public String getSensorCode() { } public static NXTSensor.Sensor getSensorFromSensorCode(String sensorCode) { - if (sensorCode == null) { - return Sensor.NO_SENSOR; - } - - try { - return valueOf(sensorCode); - } catch (IllegalArgumentException e) { - return Sensor.NO_SENSOR; - } + Sensor sensor = EnumUtils.getEnum(Sensor.class, sensorCode); + return sensor != null ? sensor : Sensor.NO_SENSOR; } } diff --git a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/Formula.java b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/Formula.java index 54a55726341..442de82c687 100644 --- a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/Formula.java +++ b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/Formula.java @@ -26,35 +26,25 @@ import org.catrobat.catroid.CatroidApplication; import org.catrobat.catroid.ProjectManager; -import org.catrobat.catroid.R; import org.catrobat.catroid.content.Project; import org.catrobat.catroid.content.Sprite; import org.catrobat.catroid.formulaeditor.FormulaElement.ElementType; +import org.catrobat.catroid.utils.EnumUtils; +import org.jetbrains.annotations.NotNull; import java.io.Serializable; import java.util.Set; -import static org.catrobat.catroid.formulaeditor.Functions.getFunctionByValue; -import static org.catrobat.catroid.utils.NumberFormats.stringWithoutTrailingZero; +import static org.catrobat.catroid.utils.NumberFormats.trimTrailingCharacters; public class Formula implements Serializable { private static final long serialVersionUID = 1L; + private static final String ERROR_STRING = "ERROR"; private FormulaElement formulaTree; private transient InternFormula internFormula = null; - public Object readResolve() { - - if (formulaTree == null) { - formulaTree = new FormulaElement(ElementType.NUMBER, "0 ", null); - } - - internFormula = new InternFormula(formulaTree.getInternTokenList()); - - return this; - } - public Formula(FormulaElement formulaElement) { formulaTree = formulaElement; internFormula = new InternFormula(formulaTree.getInternTokenList()); @@ -62,13 +52,17 @@ public Formula(FormulaElement formulaElement) { public Formula(Integer value) { if (value < 0) { - formulaTree = new FormulaElement(ElementType.OPERATOR, Operators.MINUS.toString(), null); - formulaTree.setRightChild(new FormulaElement(ElementType.NUMBER, Long.toString(Math.abs((long) value)), - formulaTree)); - internFormula = new InternFormula(formulaTree.getInternTokenList()); + initInverted(Long.toString(Math.abs((long) value))); + } else { + init(ElementType.NUMBER, value.toString()); + } + } + + public Formula(Double value) { + if (value < 0) { + initInverted(Double.toString(Math.abs(value))); } else { - formulaTree = new FormulaElement(ElementType.NUMBER, value.toString(), null); - internFormula = new InternFormula(formulaTree.getInternTokenList()); + init(ElementType.NUMBER, value.toString()); } } @@ -76,18 +70,27 @@ public Formula(Float value) { this(Double.valueOf(value)); } - public Formula(Double value) { - if (value < 0) { - formulaTree = new FormulaElement(ElementType.OPERATOR, Operators.MINUS.toString(), null); - formulaTree.setRightChild(new FormulaElement(ElementType.NUMBER, Double.toString(Math.abs(value)), - formulaTree)); - internFormula = new InternFormula(formulaTree.getInternTokenList()); + public Formula(String value) { + if (value.equalsIgnoreCase(Functions.ARDUINOANALOG.toString())) { + formulaTree = new FormulaElement(ElementType.SENSOR, Functions.ARDUINOANALOG.toString(), null); + } else if (value.equalsIgnoreCase(Functions.ARDUINODIGITAL.toString())) { + formulaTree = new FormulaElement(ElementType.SENSOR, Functions.ARDUINODIGITAL.toString(), null); } else { - formulaTree = new FormulaElement(ElementType.NUMBER, value.toString(), null); - internFormula = new InternFormula(formulaTree.getInternTokenList()); + init(ElementType.STRING, value); } } + private void init(ElementType number, String s) { + formulaTree = new FormulaElement(number, s, null); + internFormula = new InternFormula(formulaTree.getInternTokenList()); + } + + private void initInverted(String value) { + formulaTree = new FormulaElement(ElementType.OPERATOR, Operators.MINUS.toString(), null); + formulaTree.setRightChild(new FormulaElement(ElementType.NUMBER, value, formulaTree)); + internFormula = new InternFormula(formulaTree.getInternTokenList()); + } + public void updateCollisionFormulas(String oldName, String newName, Context context) { internFormula.updateCollisionFormula(oldName, newName, context); formulaTree.updateCollisionFormula(oldName, newName); @@ -95,7 +98,7 @@ public void updateCollisionFormulas(String oldName, String newName, Context cont public void updateCollisionFormulasToVersion() { internFormula.updateCollisionFormulaToVersion(CatroidApplication.getAppContext()); - formulaTree.updateCollisionFormulaToVersion(); + formulaTree.updateCollisionFormulaToVersion(ProjectManager.getInstance().getCurrentProject()); } public void updateVariableName(String oldName, String newName) { @@ -112,54 +115,48 @@ public boolean containsSpriteInCollision(String name) { return formulaTree.containsSpriteInCollision(name); } - public Formula(String value) { - if (value.equalsIgnoreCase(Functions.ARDUINOANALOG.toString())) { - formulaTree = new FormulaElement(ElementType.SENSOR, Functions.ARDUINOANALOG.toString(), null); - } else if (value.equalsIgnoreCase(Functions.ARDUINODIGITAL.toString())) { - formulaTree = new FormulaElement(ElementType.SENSOR, Functions.ARDUINODIGITAL.toString(), null); - } else { - formulaTree = new FormulaElement(ElementType.STRING, value, null); - internFormula = new InternFormula(formulaTree.getInternTokenList()); + public Integer interpretInteger(Sprite sprite) throws InterpretationException { + return interpretDouble(sprite).intValue(); + } + + @NotNull + private String tryInterpretDouble(Sprite sprite) { + try { + return String.valueOf(interpretDouble(sprite)); + } catch (InterpretationException interpretationException) { + return ERROR_STRING; } } - public Boolean interpretBoolean(Sprite sprite) throws InterpretationException { - int result = interpretDouble(sprite).intValue(); - return result != 0; + public Double interpretDouble(Sprite sprite) throws InterpretationException { + try { + return assertNotNaN(interpretDoubleInternal(sprite)); + } catch (ClassCastException | NumberFormatException exception) { + throw new InterpretationException("Couldn't interpret Formula.", exception); + } } - public Integer interpretInteger(Sprite sprite) throws InterpretationException { - Double returnValue = interpretDouble(sprite); - return returnValue.intValue(); + @NotNull + private Double interpretDoubleInternal(Sprite sprite) { + Object o = formulaTree.interpretRecursive(sprite); + Double doubleReturnValue; + if (o instanceof String) { + doubleReturnValue = Double.valueOf((String) o); + } else { + doubleReturnValue = (Double) o; + } + return doubleReturnValue; } - public Double interpretDouble(Sprite sprite) throws InterpretationException { - try { - Object returnValue = formulaTree.interpretRecursive(sprite); - Double doubleReturnValue; - if (returnValue instanceof String) { - doubleReturnValue = Double.valueOf((String) returnValue); - if (doubleReturnValue.isNaN()) { - throw new InterpretationException("NaN in interpretDouble()"); - } - return doubleReturnValue; - } else { - doubleReturnValue = (Double) returnValue; - if (doubleReturnValue.isNaN()) { - throw new InterpretationException("NaN in interpretDouble()"); - } - return (Double) returnValue; - } - } catch (ClassCastException classCastException) { - throw new InterpretationException("Couldn't interpret Formula.", classCastException); - } catch (NumberFormatException numberFormatException) { - throw new InterpretationException("Couldn't interpret Formula.", numberFormatException); + private double assertNotNaN(Double doubleReturnValue) throws InterpretationException { + if (doubleReturnValue.isNaN()) { + throw new InterpretationException("NaN in interpretDouble()"); } + return doubleReturnValue; } public Float interpretFloat(Sprite sprite) throws InterpretationException { - Double returnValue = interpretDouble(sprite); - return returnValue.floatValue(); + return interpretDouble(sprite).floatValue(); } public String interpretString(Sprite sprite) throws InterpretationException { @@ -170,7 +167,7 @@ public String interpretString(Sprite sprite) throws InterpretationException { } String value = String.valueOf(interpretation); - return stringWithoutTrailingZero(value); + return trimTrailingCharacters(value); } public Object interpretObject(Sprite sprite) { @@ -215,48 +212,79 @@ public void addRequiredResources(final Set requiredResourcesSet) { formulaTree.addRequiredResources(requiredResourcesSet); } - public String getResultForComputeDialog(Context context) { - Sprite sprite = ProjectManager.getInstance().getCurrentSprite(); + public String getResultForComputeDialog(StringProvider stringProvider, Sprite sprite) { ElementType type = formulaTree.getElementType(); + Project currentProject = ProjectManager.getInstance().getCurrentProject(); + Sprite currentSprite = ProjectManager.getInstance().getCurrentSprite(); if (formulaTree.isLogicalOperator()) { - boolean result; - try { - result = this.interpretBoolean(sprite); - } catch (InterpretationException interpretationException) { - return "ERROR"; - } - int logicalFormulaResultIdentifier = result ? R.string.formula_editor_true : R.string.formula_editor_false; - return context.getString(logicalFormulaResultIdentifier); - } else if (type == ElementType.STRING + return tryInterpretBooleanToString(stringProvider, sprite); + } else if (isStringInterpretableType(type, formulaTree.getValue())) { + return tryInterpretString(sprite); + } else if (isVariableWithTypeString(sprite, currentProject)) { + return interpretUserVariable(currentProject, currentSprite); + } else { + return tryInterpretDouble(sprite); + } + } + + private boolean isStringInterpretableType(ElementType type, String formulaValue) { + return type == ElementType.STRING || type == ElementType.SENSOR - || type == ElementType.USER_LIST - || (type == ElementType.FUNCTION - && (Functions.TEXT.contains(getFunctionByValue(formulaTree.getValue())) - || Functions.LIST.contains(getFunctionByValue(formulaTree.getValue()))))) { - try { - return interpretString(sprite); - } catch (InterpretationException interpretationException) { - return "ERROR"; - } - } else if (formulaTree.isUserVariableWithTypeString(sprite)) { - Project currentProject = ProjectManager.getInstance().getCurrentProject(); - Sprite currentSprite = ProjectManager.getInstance().getCurrentSprite(); - UserVariable userVariable = UserDataWrapper - .getUserVariable(formulaTree.getValue(), currentSprite, currentProject); - return (String) userVariable.getValue(); + || type == ElementType.FUNCTION && isInterpretableFunction(formulaValue); + } + + private boolean isInterpretableFunction(String formulaValue) { + Functions function = EnumUtils.getEnum(Functions.class, formulaValue); + return function == Functions.LETTER || function == Functions.JOIN || function == Functions.REGEX; + } + + private boolean isVariableWithTypeString(Sprite sprite, Project currentProject) { + if (formulaTree.getElementType() == ElementType.USER_VARIABLE) { + UserVariable userVariable = UserDataWrapper.getUserVariable(formulaTree.getValue(), sprite, currentProject); + return userVariable.getValue() instanceof String; } else { - Double interpretationResult; - try { - interpretationResult = this.interpretDouble(sprite); - } catch (InterpretationException interpretationException) { - return "ERROR"; - } - return String.valueOf(interpretationResult); + return false; } } - public FormulaElement getFormulaTree() { - return formulaTree; + private String interpretUserVariable(Project currentProject, Sprite currentSprite) { + UserVariable userVariable = UserDataWrapper + .getUserVariable(formulaTree.getValue(), currentSprite, currentProject); + return (String) userVariable.getValue(); + } + + private String tryInterpretString(Sprite sprite) { + try { + return interpretString(sprite); + } catch (InterpretationException interpretationException) { + return ERROR_STRING; + } + } + + private String tryInterpretBooleanToString(StringProvider stringProvider, Sprite sprite) { + try { + return interpretBooleanToString(sprite, stringProvider); + } catch (InterpretationException interpretationException) { + return ERROR_STRING; + } + } + + public String interpretBooleanToString(Sprite sprite, StringProvider stringProvider) throws InterpretationException { + boolean booleanValue = interpretBoolean(sprite); + return toLocalizedString(booleanValue, stringProvider); + } + + public boolean interpretBoolean(Sprite sprite) throws InterpretationException { + return interpretDouble(sprite).intValue() != 0; + } + + private String toLocalizedString(boolean value, StringProvider stringProvider) { + return value ? stringProvider.getTrue() : stringProvider.getFalse(); + } + + public interface StringProvider { + String getTrue(); + String getFalse(); } } diff --git a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/FormulaElement.java b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/FormulaElement.java index 14c2dad41ad..531d0b4fc6e 100644 --- a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/FormulaElement.java +++ b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/FormulaElement.java @@ -22,36 +22,65 @@ */ package org.catrobat.catroid.formulaeditor; -import android.content.res.Resources; - import org.catrobat.catroid.ProjectManager; -import org.catrobat.catroid.bluetooth.base.BluetoothDevice; -import org.catrobat.catroid.common.CatroidService; -import org.catrobat.catroid.common.Constants; -import org.catrobat.catroid.common.LookData; -import org.catrobat.catroid.common.ServiceProvider; -import org.catrobat.catroid.content.GroupSprite; -import org.catrobat.catroid.content.Look; import org.catrobat.catroid.content.Project; +import org.catrobat.catroid.content.Scene; import org.catrobat.catroid.content.Sprite; import org.catrobat.catroid.content.bricks.Brick; -import org.catrobat.catroid.devices.arduino.Arduino; -import org.catrobat.catroid.devices.raspberrypi.RPiSocketConnection; -import org.catrobat.catroid.devices.raspberrypi.RaspberryPiService; -import org.catrobat.catroid.nfc.NfcHandler; +import org.catrobat.catroid.formulaeditor.function.ArduinoFunctionProvider; +import org.catrobat.catroid.formulaeditor.function.BinaryFunction; +import org.catrobat.catroid.formulaeditor.function.FormulaFunction; +import org.catrobat.catroid.formulaeditor.function.FunctionProvider; +import org.catrobat.catroid.formulaeditor.function.MathFunctionProvider; +import org.catrobat.catroid.formulaeditor.function.RaspiFunctionProvider; +import org.catrobat.catroid.formulaeditor.function.TouchFunctionProvider; import org.catrobat.catroid.sensing.CollisionDetection; import org.catrobat.catroid.stage.StageActivity; -import org.catrobat.catroid.utils.TouchUtil; +import org.catrobat.catroid.stage.StageListener; +import org.jetbrains.annotations.NotNull; import java.io.Serializable; -import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static org.catrobat.catroid.utils.NumberFormats.stringWithoutTrailingZero; +import androidx.annotation.Nullable; + +import static org.catrobat.catroid.formulaeditor.InternTokenType.BRACKET_CLOSE; +import static org.catrobat.catroid.formulaeditor.InternTokenType.BRACKET_OPEN; +import static org.catrobat.catroid.formulaeditor.InternTokenType.COLLISION_FORMULA; +import static org.catrobat.catroid.formulaeditor.InternTokenType.FUNCTION_NAME; +import static org.catrobat.catroid.formulaeditor.InternTokenType.FUNCTION_PARAMETERS_BRACKET_CLOSE; +import static org.catrobat.catroid.formulaeditor.InternTokenType.FUNCTION_PARAMETERS_BRACKET_OPEN; +import static org.catrobat.catroid.formulaeditor.InternTokenType.FUNCTION_PARAMETER_DELIMITER; +import static org.catrobat.catroid.formulaeditor.InternTokenType.NUMBER; +import static org.catrobat.catroid.formulaeditor.InternTokenType.OPERATOR; +import static org.catrobat.catroid.formulaeditor.InternTokenType.SENSOR; +import static org.catrobat.catroid.formulaeditor.InternTokenType.STRING; +import static org.catrobat.catroid.formulaeditor.InternTokenType.USER_LIST; +import static org.catrobat.catroid.formulaeditor.InternTokenType.USER_VARIABLE; +import static org.catrobat.catroid.formulaeditor.common.Conversions.FALSE; +import static org.catrobat.catroid.formulaeditor.common.Conversions.TRUE; +import static org.catrobat.catroid.formulaeditor.common.Conversions.booleanToDouble; +import static org.catrobat.catroid.formulaeditor.common.Conversions.convertArgumentToDouble; +import static org.catrobat.catroid.formulaeditor.common.FormulaElementOperations.interpretOperatorEqual; +import static org.catrobat.catroid.formulaeditor.common.FormulaElementOperations.interpretSensor; +import static org.catrobat.catroid.formulaeditor.common.FormulaElementOperations.interpretUserList; +import static org.catrobat.catroid.formulaeditor.common.FormulaElementOperations.interpretUserVariable; +import static org.catrobat.catroid.formulaeditor.common.FormulaElementOperations.isInteger; +import static org.catrobat.catroid.formulaeditor.common.FormulaElementOperations.normalizeDegeneratedDoubleValues; +import static org.catrobat.catroid.formulaeditor.common.FormulaElementOperations.tryInterpretCollision; +import static org.catrobat.catroid.formulaeditor.common.FormulaElementOperations.tryInterpretDoubleValue; +import static org.catrobat.catroid.formulaeditor.common.FormulaElementOperations.tryInterpretElementRecursive; +import static org.catrobat.catroid.formulaeditor.common.FormulaElementOperations.tryParseIntFromObject; +import static org.catrobat.catroid.formulaeditor.common.FormulaElementResources.addFunctionResources; +import static org.catrobat.catroid.formulaeditor.common.FormulaElementResources.addSensorsResources; +import static org.catrobat.catroid.utils.NumberFormats.trimTrailingCharacters; public class FormulaElement implements Serializable { @@ -61,17 +90,23 @@ public enum ElementType { OPERATOR, FUNCTION, NUMBER, SENSOR, USER_VARIABLE, USER_LIST, BRACKET, STRING, COLLISION_FORMULA } - public static final Double NOT_EXISTING_USER_VARIABLE_INTERPRETATION_VALUE = 0d; - public static final Double NOT_EXISTING_USER_LIST_INTERPRETATION_VALUE = 0d; - private static final String EMPTY_USER_LIST_INTERPRETATION_VALUE = ""; - private ElementType type; private String value; private FormulaElement leftChild = null; private FormulaElement rightChild = null; - private transient FormulaElement parent = null; + private transient FormulaElement parent; + private transient Map formulaFunctions; + + protected FormulaElement() { + List functionProviders = Arrays.asList(new ArduinoFunctionProvider(), new RaspiFunctionProvider(), + new MathFunctionProvider(), new TouchFunctionProvider()); + + formulaFunctions = new EnumMap<>(Functions.class); + initFunctionMap(functionProviders, formulaFunctions); + } public FormulaElement(ElementType type, String value, FormulaElement parent) { + this(); this.type = type; this.value = value; this.parent = parent; @@ -79,9 +114,7 @@ public FormulaElement(ElementType type, String value, FormulaElement parent) { public FormulaElement(ElementType type, String value, FormulaElement parent, FormulaElement leftChild, FormulaElement rightChild) { - this.type = type; - this.value = value; - this.parent = parent; + this(type, value, parent); this.leftChild = leftChild; this.rightChild = rightChild; @@ -93,70 +126,98 @@ public FormulaElement(ElementType type, String value, FormulaElement parent, For } } + private void initFunctionMap(List functionProviders, Map formulaFunctions) { + for (FunctionProvider functionProvider : functionProviders) { + functionProvider.addFunctionsToMap(formulaFunctions); + } + + formulaFunctions.put(Functions.RAND, new BinaryFunction(this::interpretFunctionRand)); + } + public ElementType getElementType() { return type; } public String getValue() { - return stringWithoutTrailingZero(value); + return trimTrailingCharacters(value); } public List getInternTokenList() { - List internTokenList = new LinkedList<>(); + List tokens = new LinkedList<>(); switch (type) { case BRACKET: - internTokenList.add(new InternToken(InternTokenType.BRACKET_OPEN)); - if (rightChild != null) { - internTokenList.addAll(rightChild.getInternTokenList()); - } - internTokenList.add(new InternToken(InternTokenType.BRACKET_CLOSE)); + addBracketTokens(tokens, rightChild); break; case OPERATOR: - if (leftChild != null) { - internTokenList.addAll(leftChild.getInternTokenList()); - } - internTokenList.add(new InternToken(InternTokenType.OPERATOR, this.value)); - if (rightChild != null) { - internTokenList.addAll(rightChild.getInternTokenList()); - } + addOperatorTokens(tokens, value); break; case FUNCTION: - internTokenList.add(new InternToken(InternTokenType.FUNCTION_NAME, value)); - boolean functionHasParameters = false; - if (leftChild != null) { - internTokenList.add(new InternToken(InternTokenType.FUNCTION_PARAMETERS_BRACKET_OPEN)); - functionHasParameters = true; - internTokenList.addAll(leftChild.getInternTokenList()); - } - if (rightChild != null) { - internTokenList.add(new InternToken(InternTokenType.FUNCTION_PARAMETER_DELIMITER)); - internTokenList.addAll(rightChild.getInternTokenList()); - } - if (functionHasParameters) { - internTokenList.add(new InternToken(InternTokenType.FUNCTION_PARAMETERS_BRACKET_CLOSE)); - } + addFunctionTokens(tokens, value, leftChild, rightChild); break; case USER_VARIABLE: - internTokenList.add(new InternToken(InternTokenType.USER_VARIABLE, this.value)); + addToken(tokens, USER_VARIABLE, value); break; case USER_LIST: - internTokenList.add(new InternToken(InternTokenType.USER_LIST, this.value)); + addToken(tokens, USER_LIST, value); break; case NUMBER: - internTokenList.add(new InternToken(InternTokenType.NUMBER, stringWithoutTrailingZero(this.value))); + addToken(tokens, NUMBER, trimTrailingCharacters(value)); break; case SENSOR: - internTokenList.add(new InternToken(InternTokenType.SENSOR, this.value)); + addToken(tokens, SENSOR, value); break; case STRING: - internTokenList.add(new InternToken(InternTokenType.STRING, value)); + addToken(tokens, STRING, value); break; case COLLISION_FORMULA: - internTokenList.add(new InternToken(InternTokenType.COLLISION_FORMULA, this.value)); + addToken(tokens, COLLISION_FORMULA, value); break; } - return internTokenList; + return tokens; + } + + private void addToken(List tokens, InternTokenType tokenType) { + tokens.add(new InternToken(tokenType)); + } + + private void addToken(List tokens, InternTokenType tokenType, String value) { + tokens.add(new InternToken(tokenType, value)); + } + + private void addBracketTokens(List internTokenList, FormulaElement element) { + addToken(internTokenList, BRACKET_OPEN); + tryAddInternTokens(internTokenList, element); + addToken(internTokenList, BRACKET_CLOSE); + } + + private void addOperatorTokens(List tokens, String value) { + tryAddInternTokens(tokens, leftChild); + addToken(tokens, OPERATOR, value); + tryAddInternTokens(tokens, rightChild); + } + + private void addFunctionTokens(List tokens, String value, FormulaElement leftChild, FormulaElement rightChild) { + addToken(tokens, FUNCTION_NAME, value); + boolean functionHasParameters = false; + if (leftChild != null) { + addToken(tokens, FUNCTION_PARAMETERS_BRACKET_OPEN); + functionHasParameters = true; + tokens.addAll(leftChild.getInternTokenList()); + } + if (rightChild != null) { + addToken(tokens, FUNCTION_PARAMETER_DELIMITER); + tokens.addAll(rightChild.getInternTokenList()); + } + if (functionHasParameters) { + addToken(tokens, FUNCTION_PARAMETERS_BRACKET_CLOSE); + } + } + + private void tryAddInternTokens(List tokens, FormulaElement child) { + if (child != null) { + tokens.addAll(child.getInternTokenList()); + } } public FormulaElement getRoot() { @@ -168,604 +229,345 @@ public FormulaElement getRoot() { } public void updateVariableReferences(String oldName, String newName) { - if (leftChild != null) { - leftChild.updateVariableReferences(oldName, newName); - } - if (rightChild != null) { - rightChild.updateVariableReferences(oldName, newName); - } - if (type == ElementType.USER_VARIABLE && value.equals(oldName)) { + tryUpdateVariableReference(leftChild, oldName, newName); + tryUpdateVariableReference(rightChild, oldName, newName); + if (matchesTypeAndName(ElementType.USER_VARIABLE, oldName)) { value = newName; } } public void updateListName(String oldName, String newName) { - if (leftChild != null) { - leftChild.updateVariableReferences(oldName, newName); - } - if (rightChild != null) { - rightChild.updateVariableReferences(oldName, newName); - } - if (type == ElementType.USER_LIST && value.equals(oldName)) { + tryUpdateVariableReference(leftChild, oldName, newName); + tryUpdateVariableReference(rightChild, oldName, newName); + if (matchesTypeAndName(ElementType.USER_LIST, oldName)) { value = newName; } } - public void getVariableAndListNames(List variables, List lists) { - if (leftChild != null) { - leftChild.getVariableAndListNames(variables, lists); - } - if (rightChild != null) { - rightChild.getVariableAndListNames(variables, lists); - } - if (type == ElementType.USER_VARIABLE && !variables.contains(value)) { - variables.add(value); - } - if (type == ElementType.USER_LIST && !lists.contains(value)) { - lists.add(value); + private void tryUpdateVariableReference(FormulaElement element, String oldName, String newName) { + if (element != null) { + element.updateVariableReferences(oldName, newName); } } - public boolean containsSpriteInCollision(String name) { - boolean contained = false; - if (leftChild != null) { - contained |= leftChild.containsSpriteInCollision(name); - } - if (rightChild != null) { - contained |= rightChild.containsSpriteInCollision(name); - } - if (type == ElementType.COLLISION_FORMULA && value.equals(name)) { - contained = true; - } - return contained; + public final boolean containsSpriteInCollision(String name) { + return containsSpriteInCollision(leftChild, name) + || containsSpriteInCollision(rightChild, name) + || matchesTypeAndName(ElementType.COLLISION_FORMULA, name); } - public void updateCollisionFormula(String oldName, String newName) { + private boolean containsSpriteInCollision(FormulaElement element, String name) { + return element != null && element.containsSpriteInCollision(name); + } - if (leftChild != null) { - leftChild.updateCollisionFormula(oldName, newName); - } - if (rightChild != null) { - rightChild.updateCollisionFormula(oldName, newName); - } - if (type == ElementType.COLLISION_FORMULA && value.equals(oldName)) { + public final void updateCollisionFormula(String oldName, String newName) { + tryUpdateCollisionFormula(leftChild, oldName, newName); + tryUpdateCollisionFormula(rightChild, oldName, newName); + if (matchesTypeAndName(ElementType.COLLISION_FORMULA, oldName)) { value = newName; } } - public void updateCollisionFormulaToVersion() { - if (leftChild != null) { - leftChild.updateCollisionFormulaToVersion(); - } - if (rightChild != null) { - rightChild.updateCollisionFormulaToVersion(); + private boolean matchesTypeAndName(ElementType collisionFormula, String name) { + return type == collisionFormula && value.equals(name); + } + + private void tryUpdateCollisionFormula(FormulaElement element, String oldName, String newName) { + if (element != null) { + element.updateCollisionFormula(oldName, newName); } + } + + public void updateCollisionFormulaToVersion(Project currentProject) { + tryUpdateCollisionFormulaToVersion(leftChild, currentProject); + tryUpdateCollisionFormulaToVersion(rightChild, currentProject); if (type == ElementType.COLLISION_FORMULA) { - String secondSpriteName = CollisionDetection.getSecondSpriteNameFromCollisionFormulaString(value); + String secondSpriteName = CollisionDetection.getSecondSpriteNameFromCollisionFormulaString(value, currentProject); if (secondSpriteName != null) { value = secondSpriteName; } } } + private void tryUpdateCollisionFormulaToVersion(FormulaElement element, Project currentProject) { + if (element != null) { + element.updateCollisionFormulaToVersion(currentProject); + } + } + public Object interpretRecursive(Sprite sprite) { + Object rawReturnValue = rawInterpretRecursive(sprite); + return normalizeDegeneratedDoubleValues(rawReturnValue); + } - Object returnValue = 0d; + private Object rawInterpretRecursive(Sprite sprite) { + ProjectManager projectManager = ProjectManager.getInstance(); + Project currentProject = projectManager != null ? projectManager.getCurrentProject() : null; + Scene currentlyPlayingScene = projectManager != null ? projectManager.getCurrentlyPlayingScene() : null; + Scene currentlyEditedScene = projectManager != null ? projectManager.getCurrentlyEditedScene() : null; switch (type) { case BRACKET: - returnValue = rightChild.interpretRecursive(sprite); - break; + return rightChild.interpretRecursive(sprite); case NUMBER: - returnValue = value; - break; + case STRING: + return value; case OPERATOR: - Operators operator = Operators.getOperatorByValue(value); - returnValue = interpretOperator(operator, sprite); - break; + return tryInterpretOperator(sprite, value); case FUNCTION: Functions function = Functions.getFunctionByValue(value); - returnValue = interpretFunction(function, sprite); - break; + return interpretFunction(function, sprite, currentProject); case SENSOR: - returnValue = interpretSensor(sprite); - break; + return interpretSensor(sprite, currentlyEditedScene, currentProject, value); case USER_VARIABLE: - returnValue = interpretUserVariable(sprite); - break; + UserVariable userVariable = UserDataWrapper.getUserVariable(value, sprite, currentProject); + return interpretUserVariable(userVariable); case USER_LIST: - returnValue = interpretUserList(sprite); - break; - case STRING: - returnValue = value; - break; + UserList userList = UserDataWrapper.getUserList(value, sprite, currentProject); + return interpretUserList(userList); case COLLISION_FORMULA: - try { - returnValue = interpretCollision(sprite, value); - } catch (Exception exception) { - returnValue = 0d; - } - } - return normalizeDegeneratedDoubleValues(returnValue); - } - - private Object interpretCollision(Sprite firstSprite, String formula) { - - String secondSpriteName = formula; - Sprite secondSprite; - try { - secondSprite = ProjectManager.getInstance().getCurrentlyPlayingScene().getSprite(secondSpriteName); - } catch (Resources.NotFoundException exception) { - return 0d; - } - Look firstLook = firstSprite.look; - Look secondLook; - if (secondSprite instanceof GroupSprite) { - List groupSprites = GroupSprite.getSpritesFromGroupWithGroupName(secondSpriteName); - for (Sprite sprite : groupSprites) { - secondLook = sprite.look; - if (CollisionDetection.checkCollisionBetweenLooks(firstLook, secondLook) == 1d) { - return 1d; - } - } - return 0d; - } - - List spriteAndClones = new ArrayList<>(); - spriteAndClones.add(secondSprite); - if (StageActivity.stageListener != null) { - spriteAndClones.addAll(StageActivity.stageListener.getAllClonesOfSprite(secondSprite)); - } - - for (Sprite sprite : spriteAndClones) { - secondLook = sprite.look; - if (firstLook.equals(secondLook)) { - continue; - } - - if (CollisionDetection.checkCollisionBetweenLooks(firstLook, secondLook) == 1d) { - return 1d; - } - } - - return 0d; - } - - private Object interpretUserList(Sprite sprite) { - Project currentProject = ProjectManager.getInstance().getCurrentProject(); - UserList userList = UserDataWrapper.getUserList(value, sprite, currentProject); - if (userList == null) { - return NOT_EXISTING_USER_LIST_INTERPRETATION_VALUE; - } - - List userListValues = userList.getValue(); - - if (userListValues.size() == 0) { - return EMPTY_USER_LIST_INTERPRETATION_VALUE; - } else if (userListValues.size() == 1) { - return userListValues.get(0); - } else { - return interpretMultipleItemsUserList(userListValues); + StageListener stageListener = StageActivity.stageListener; + return tryInterpretCollision(sprite.look, value, currentlyPlayingScene, stageListener); } + return FALSE; } - private Object interpretMultipleItemsUserList(List userListValues) { - List userListStringValues = new ArrayList<>(); - - for (Object listValue : userListValues) { - if (listValue instanceof Double) { - Double doubleValueOfListItem = (Double) listValue; - userListStringValues.add(stringWithoutTrailingZero(String.valueOf(doubleValueOfListItem.intValue()))); - } else if (listValue instanceof String) { - String stringValueOfListItem = (String) listValue; - userListStringValues.add(stringWithoutTrailingZero(stringValueOfListItem)); - } - } - - StringBuilder stringBuilder = new StringBuilder(userListStringValues.size()); - String separator = listConsistsOfSingleCharacters(userListStringValues) ? "" : " "; - for (String userListStringValue : userListStringValues) { - stringBuilder.append(stringWithoutTrailingZero(userListStringValue)); - stringBuilder.append(separator); + @NotNull + private Object tryInterpretOperator(Sprite sprite, String value) { + Operators operator = Operators.getOperatorByValue(value); + if (operator == null) { + return false; } - - return stringBuilder.toString().trim(); + return interpretOperator(operator, sprite); } - private Boolean listConsistsOfSingleCharacters(List userListStringValues) { - for (String userListStringValue : userListStringValues) { - if (userListStringValue.length() > 1) { - return false; - } - } - return true; - } - - private Object interpretUserVariable(Sprite sprite) { - Project currentProject = ProjectManager.getInstance().getCurrentProject(); - UserVariable userVariable = UserDataWrapper.getUserVariable(value, sprite, currentProject); - if (userVariable == null) { - return NOT_EXISTING_USER_VARIABLE_INTERPRETATION_VALUE; - } - return userVariable.getValue(); - } - - private Object interpretSensor(Sprite sprite) { - Sensors sensor = Sensors.getSensorByValue(value); - if (sensor.isObjectSensor) { - return interpretObjectSensor(sensor, sprite); - } else { - return SensorHandler.getSensorValue(sensor); - } - } - - private Object interpretFunction(Functions function, Sprite sprite) { - final double defaultReturnValue = 0d; - - Object left = null; - Object right = null; - Double doubleValueOfLeftChild = null; - Double doubleValueOfRightChild = null; - - if (leftChild != null) { - left = leftChild.interpretRecursive(sprite); - if (left instanceof String) { - try { - doubleValueOfLeftChild = Double.valueOf((String) left); - } catch (NumberFormatException numberFormatException) { - //This is expected - } - } else { - doubleValueOfLeftChild = (Double) left; - } - } - - if (rightChild != null) { - right = rightChild.interpretRecursive(sprite); - if (right instanceof String) { - try { - doubleValueOfRightChild = Double.valueOf((String) right); - } catch (NumberFormatException numberFormatException) { - //This is expected - } - } else { - doubleValueOfRightChild = (Double) right; - } - } + private Object interpretFunction(Functions function, Sprite sprite, Project currentProject) { + Object firstArgument = tryInterpretRecursive(leftChild, sprite); + Object secondArgument = tryInterpretRecursive(rightChild, sprite); switch (function) { - case SIN: - return doubleValueOfLeftChild == null ? defaultReturnValue : java.lang.Math.sin(Math.toRadians(doubleValueOfLeftChild)); - case COS: - return doubleValueOfLeftChild == null ? defaultReturnValue : java.lang.Math.cos(Math.toRadians(doubleValueOfLeftChild)); - case TAN: - return doubleValueOfLeftChild == null ? defaultReturnValue : java.lang.Math.tan(Math.toRadians(doubleValueOfLeftChild)); - case LN: - return doubleValueOfLeftChild == null ? defaultReturnValue : java.lang.Math.log(doubleValueOfLeftChild); - case LOG: - return doubleValueOfLeftChild == null ? defaultReturnValue : java.lang.Math.log10(doubleValueOfLeftChild); - case SQRT: - return doubleValueOfLeftChild == null ? defaultReturnValue : java.lang.Math.sqrt(doubleValueOfLeftChild); - case RAND: - return (doubleValueOfLeftChild == null || doubleValueOfRightChild == null) ? defaultReturnValue - : interpretFunctionRand(doubleValueOfLeftChild, doubleValueOfRightChild); - case ABS: - return doubleValueOfLeftChild == null ? defaultReturnValue : java.lang.Math.abs(doubleValueOfLeftChild); - case ROUND: - return doubleValueOfLeftChild == null ? defaultReturnValue : (double) java.lang.Math.round(doubleValueOfLeftChild); - case PI: - return java.lang.Math.PI; - case MOD: - return (doubleValueOfLeftChild == null || doubleValueOfRightChild == null) ? defaultReturnValue - : interpretFunctionMod(doubleValueOfLeftChild, doubleValueOfRightChild); - case ARCSIN: - return doubleValueOfLeftChild == null ? defaultReturnValue : java.lang.Math.toDegrees(Math.asin(doubleValueOfLeftChild)); - case ARCCOS: - return doubleValueOfLeftChild == null ? defaultReturnValue : java.lang.Math.toDegrees(Math.acos(doubleValueOfLeftChild)); - case ARCTAN: - return doubleValueOfLeftChild == null ? defaultReturnValue : java.lang.Math.toDegrees(Math.atan(doubleValueOfLeftChild)); - case ARCTAN2: - if (doubleValueOfLeftChild == null || doubleValueOfRightChild == null) { - return defaultReturnValue; - } - if ((doubleValueOfLeftChild == 0) && (doubleValueOfRightChild == 0)) { - return -180 + java.lang.Math.random() * 360; - } else { - return java.lang.Math.toDegrees(java.lang.Math.atan2(doubleValueOfLeftChild, doubleValueOfRightChild)); - } - case EXP: - return doubleValueOfLeftChild == null ? defaultReturnValue : java.lang.Math.exp(doubleValueOfLeftChild); - case POWER: - return (doubleValueOfLeftChild == null || doubleValueOfRightChild == null) ? defaultReturnValue - : java.lang.Math.pow(doubleValueOfLeftChild, doubleValueOfRightChild); - case FLOOR: - return doubleValueOfLeftChild == null ? defaultReturnValue : java.lang.Math.floor(doubleValueOfLeftChild); - case CEIL: - return doubleValueOfLeftChild == null ? defaultReturnValue : java.lang.Math.ceil(doubleValueOfLeftChild); - case MAX: - return (doubleValueOfLeftChild == null || doubleValueOfRightChild == null) ? defaultReturnValue : java.lang.Math.max(doubleValueOfLeftChild, - doubleValueOfRightChild); - case MIN: - return (doubleValueOfLeftChild == null || doubleValueOfRightChild == null) ? defaultReturnValue : java.lang.Math.min(doubleValueOfLeftChild, - doubleValueOfRightChild); - case TRUE: - return 1d; - case FALSE: - return 0d; case LETTER: - return interpretFunctionLetter(right, left); + return interpretFunctionLetter(firstArgument, secondArgument); case LENGTH: - return interpretFunctionLength(left, sprite); + return interpretFunctionLength(firstArgument, sprite, currentProject); case JOIN: - return interpretFunctionJoin(sprite); + return interpretFunctionJoin(sprite, leftChild, rightChild); case REGEX: - return interpretFunctionRegex(sprite); - case ARDUINODIGITAL: - Arduino arduinoDigital = ServiceProvider.getService(CatroidService.BLUETOOTH_DEVICE_SERVICE).getDevice(BluetoothDevice.ARDUINO); - if (arduinoDigital != null && doubleValueOfLeftChild != null) { - if (doubleValueOfLeftChild < 0 || doubleValueOfLeftChild > 13) { - return defaultReturnValue; - } - return arduinoDigital.getDigitalArduinoPin(doubleValueOfLeftChild.intValue()); - } - break; - case ARDUINOANALOG: - Arduino arduinoAnalog = ServiceProvider.getService(CatroidService.BLUETOOTH_DEVICE_SERVICE).getDevice(BluetoothDevice.ARDUINO); - if (arduinoAnalog != null && doubleValueOfLeftChild != null) { - if (doubleValueOfLeftChild < 0 || doubleValueOfLeftChild > 5) { - return defaultReturnValue; - } - return arduinoAnalog.getAnalogArduinoPin(doubleValueOfLeftChild.intValue()); - } - break; - case RASPIDIGITAL: - RPiSocketConnection connection = RaspberryPiService.getInstance().connection; - if (doubleValueOfLeftChild != null) { - int pin = doubleValueOfLeftChild.intValue(); - try { - return connection.getPin(pin) ? 1d : 0d; - } catch (Exception e) { - //This is expected - } - } - break; - case MULTI_FINGER_TOUCHED: - return (doubleValueOfLeftChild != null && TouchUtil.isFingerTouching(doubleValueOfLeftChild.intValue())) - ? 1d : defaultReturnValue; - case MULTI_FINGER_X: - return doubleValueOfLeftChild != null ? (double) TouchUtil.getX(doubleValueOfLeftChild - .intValue()) : defaultReturnValue; - case MULTI_FINGER_Y: - return doubleValueOfLeftChild != null ? (double) TouchUtil.getY(doubleValueOfLeftChild.intValue()) - : defaultReturnValue; + return tryInterpretFunctionRegex(sprite, leftChild, rightChild); case LIST_ITEM: - return interpretFunctionListItem(left, sprite); + return interpretFunctionListItem(firstArgument, sprite, currentProject); case CONTAINS: - return interpretFunctionContains(right, sprite); + return interpretFunctionContains(secondArgument, sprite, currentProject); case NUMBER_OF_ITEMS: - return interpretFunctionNumberOfItems(left, sprite); + return interpretFunctionNumberOfItems(firstArgument, sprite, currentProject); case INDEX_OF_ITEM: - return interpretFunctionIndexOfItem(left, sprite); + return interpretFunctionIndexOfItem(firstArgument, sprite, currentProject); + default: + return interpretFormulaFunction(function, firstArgument, secondArgument); } - return 0d; } - private Object interpretFunctionNumberOfItems(Object left, Sprite sprite) { - if (leftChild.type == ElementType.USER_LIST) { - return (double) handleNumberOfItemsOfUserListParameter(sprite); + @Nullable + private Object tryInterpretRecursive(FormulaElement element, Sprite sprite) { + if (element == null) { + return null; } - return interpretFunctionLength(left, sprite); + return element.interpretRecursive(sprite); } - private Object interpretFunctionContains(Object right, Sprite sprite) { - if (leftChild.getElementType() == ElementType.USER_LIST) { - Project currentProject = ProjectManager.getInstance().getCurrentProject(); + private Object interpretFormulaFunction(Functions function, Object firstArgument, Object secondArgument) { + Double firstArgumentDouble = convertArgumentToDouble(firstArgument); + Double secondArgumentDouble = convertArgumentToDouble(secondArgument); + FormulaFunction formulaFunction = formulaFunctions.get(function); + if (formulaFunction == null) { + return FALSE; + } + return formulaFunction.execute(firstArgumentDouble, secondArgumentDouble); + } + + private Object interpretFunctionNumberOfItems(Object left, Sprite sprite, Project currentProject) { + if (leftChild.type == ElementType.USER_LIST) { UserList userList = UserDataWrapper.getUserList(leftChild.value, sprite, currentProject); + return (double) handleNumberOfItemsOfUserListParameter(userList); + } + return interpretFunctionLength(left, sprite, currentProject); + } - if (userList == null) { - return 0d; - } + private int handleNumberOfItemsOfUserListParameter(UserList userList) { + if (userList == null) { + return 0; + } - for (Object userListElement : userList.getValue()) { - if (interpretOperatorEqual(userListElement, right) == 1d) { - return 1d; - } + return userList.getValue().size(); + } + + private Object interpretFunctionContains(Object right, Sprite sprite, Project currentProject) { + UserList userList = getUserListOfChild(leftChild, sprite, currentProject); + if (userList == null) { + return FALSE; + } + + for (Object userListElement : userList.getValue()) { + if (interpretOperatorEqual(userListElement, right)) { + return TRUE; } } - return 0d; + return FALSE; } - private Object interpretFunctionIndexOfItem(Object left, Sprite sprite) { + private Object interpretFunctionIndexOfItem(Object left, Sprite sprite, Project currentProject) { if (rightChild.getElementType() == ElementType.USER_LIST) { - Project currentProject = ProjectManager.getInstance().getCurrentProject(); UserList userList = UserDataWrapper.getUserList(rightChild.value, sprite, currentProject); - return (double) (userList.getIndexOf(left) + 1); } - return 0d; + return FALSE; } - private Object interpretFunctionListItem(Object left, Sprite sprite) { - UserList userList = null; - if (rightChild.getElementType() == ElementType.USER_LIST) { - Project currentProject = ProjectManager.getInstance().getCurrentProject(); - userList = UserDataWrapper.getUserList(rightChild.value, sprite, currentProject); - } - - if (userList == null) { + private Object interpretFunctionListItem(Object left, Sprite sprite, Project currentProject) { + if (left == null) { return ""; } - int index = 0; - if (left instanceof String) { - try { - Double doubleValueOfLeftChild = Double.valueOf((String) left); - index = doubleValueOfLeftChild.intValue(); - } catch (NumberFormatException numberFormatexception) { - //This is expected - } - } else if (left != null) { - index = ((Double) left).intValue(); - } else { + UserList userList = getUserListOfChild(rightChild, sprite, currentProject); + if (userList == null) { return ""; } - index--; + int index = tryParseIntFromObject(left) - 1; - if (index < 0) { - return ""; - } else if (index >= userList.getValue().size()) { + if (index < 0 || index >= userList.getValue().size()) { return ""; } - return userList.getValue().get(index); } - private Object interpretFunctionJoin(Sprite sprite) { - return stringWithoutTrailingZero(interpretInterpretFunctionStringParameter(leftChild, sprite)) - + stringWithoutTrailingZero(interpretInterpretFunctionStringParameter(rightChild, sprite)); + @Nullable + private UserList getUserListOfChild(FormulaElement child, Sprite sprite, Project currentProject) { + if (child.getElementType() != ElementType.USER_LIST) { + return null; + } + return UserDataWrapper.getUserList(child.value, sprite, currentProject); } - private Object interpretFunctionRegex(Sprite sprite) { + private static String interpretFunctionJoin(Sprite sprite, FormulaElement leftChild, FormulaElement rightChild) { + return interpretFunctionString(leftChild, sprite) + interpretFunctionString(rightChild, sprite); + } + + private static String tryInterpretFunctionRegex(Sprite sprite, FormulaElement leftChild, FormulaElement rightChild) { try { - Pattern pattern = Pattern.compile( - stringWithoutTrailingZero(interpretInterpretFunctionStringParameter(leftChild, sprite)), - Pattern.DOTALL | Pattern.MULTILINE); - - Matcher matcher = pattern.matcher( - stringWithoutTrailingZero(interpretInterpretFunctionStringParameter(rightChild, sprite))); - - if (matcher.find()) { - if (matcher.groupCount() == 0) { - return matcher.group(0); - } else { - return matcher.group(1); - } - } else { - return ""; - } + String left = interpretFunctionString(leftChild, sprite); + String right = interpretFunctionString(rightChild, sprite); + return interpretFunctionRegex(left, right); } catch (IllegalArgumentException exception) { return exception.getLocalizedMessage(); } } - private String interpretInterpretFunctionStringParameter(FormulaElement child, Sprite sprite) { + private static String interpretFunctionRegex(String patternString, String matcherString) { + Pattern pattern = Pattern.compile(patternString, Pattern.DOTALL | Pattern.MULTILINE); + Matcher matcher = pattern.matcher(matcherString); + if (matcher.find()) { + int groupIndex = matcher.groupCount() == 0 ? 0 : 1; + return matcher.group(groupIndex); + } else { + return ""; + } + } + + private static String interpretFunctionString(FormulaElement child, Sprite sprite) { + if (child == null) { + return ""; + } else if (child.getElementType() == ElementType.STRING) { + return child.getValue(); + } + Object objectInterpretation = child.interpretRecursive(sprite); String parameterInterpretation = ""; - if (child != null) { - if (child.getElementType() == ElementType.NUMBER) { - Double number = Double.valueOf((String) child.interpretRecursive(sprite)); - if (number.isNaN()) { - parameterInterpretation = ""; - } else { - if (isInteger(number)) { - parameterInterpretation += number.intValue(); - } else { - parameterInterpretation += number; - } - } - } else if (child.getElementType() == ElementType.STRING) { - parameterInterpretation = child.value; - } else { - parameterInterpretation += child.interpretRecursive(sprite); + + if (child.getElementType() == ElementType.NUMBER) { + double number = Double.parseDouble((String) objectInterpretation); + if (!Double.isNaN(number)) { + parameterInterpretation += isInteger(number) ? (int) number : number; } + } else { + parameterInterpretation += objectInterpretation; } - return parameterInterpretation; + return trimTrailingCharacters(parameterInterpretation); } - private Object interpretFunctionLength(Object left, Sprite sprite) { + private Object interpretFunctionLength(Object left, Sprite sprite, Project currentProject) { if (leftChild == null) { - return 0d; + return FALSE; } - if (leftChild.type == ElementType.NUMBER) { - return (double) leftChild.value.length(); + switch (leftChild.type) { + case NUMBER: + case STRING: + return (double) leftChild.value.length(); + case USER_VARIABLE: + UserVariable userVariable = UserDataWrapper.getUserVariable(leftChild.value, sprite, currentProject); + return (double) calculateUserVariableLength(userVariable); + case USER_LIST: + UserList userList = UserDataWrapper.getUserList(leftChild.value, sprite, currentProject); + return calculateUserListLength(userList, left, sprite); + default: + if (left instanceof Double && ((Double) left).isNaN()) { + return 0d; + } + return (double) (String.valueOf(left)).length(); } - if (leftChild.type == ElementType.STRING) { - return (double) leftChild.value.length(); + } + + private int calculateUserVariableLength(UserVariable userVariable) { + Object userVariableValue = userVariable.getValue(); + if (userVariableValue instanceof String) { + return String.valueOf(userVariableValue).length(); + } else { + if (isInteger((Double) userVariableValue)) { + return Integer.toString(((Double) userVariableValue).intValue()).length(); + } else { + return Double.toString(((Double) userVariableValue)).length(); + } } - if (leftChild.type == ElementType.USER_VARIABLE) { - return (double) handleLengthUserVariableParameter(sprite); + } + + private double calculateUserListLength(UserList userList, Object left, Sprite sprite) { + if (userList == null || userList.getValue().isEmpty()) { + return FALSE; } - if (leftChild.type == ElementType.USER_LIST) { - Project currentProject = ProjectManager.getInstance().getCurrentProject(); - UserList userList = UserDataWrapper.getUserList(leftChild.value, sprite, currentProject); - if (userList == null) { - return 0d; - } - if (userList.getValue().size() == 0) { - return 0d; - } - Object interpretedList = leftChild.interpretRecursive(sprite); - if (interpretedList instanceof Double) { - Double interpretedListDoubleValue = (Double) interpretedList; - if (interpretedListDoubleValue.isNaN() || interpretedListDoubleValue.isInfinite()) { - return 0d; - } - return (double) (String.valueOf(interpretedListDoubleValue.intValue())).length(); - } - if (interpretedList instanceof String) { - String interpretedListStringValue = (String) interpretedList; - return (double) interpretedListStringValue.length(); + Object interpretedList = leftChild.interpretRecursive(sprite); + if (interpretedList instanceof Double) { + Double interpretedListDoubleValue = (Double) interpretedList; + if (interpretedListDoubleValue.isNaN() || interpretedListDoubleValue.isInfinite()) { + return FALSE; } + return String.valueOf(interpretedListDoubleValue.intValue()).length(); + } + if (interpretedList instanceof String) { + return ((String) interpretedList).length(); } if (left instanceof Double && ((Double) left).isNaN()) { - return 0d; + return FALSE; } - return (double) (String.valueOf(left)).length(); + return String.valueOf(left).length(); } - private Object interpretFunctionLetter(Object right, Object left) { - int index = 0; - if (left instanceof String) { - try { - Double doubleValueOfLeftChild = Double.valueOf((String) left); - index = doubleValueOfLeftChild.intValue(); - } catch (NumberFormatException numberFormatexception) { - //This is expected - } - } else if (left != null) { - index = ((Double) left).intValue(); - } else { + private Object interpretFunctionLetter(Object left, Object right) { + if (left == null || right == null) { return ""; } - index--; + int index = tryParseIntFromObject(left) - 1; + String stringValueOfRight = String.valueOf(right); - if (index < 0) { - return ""; - } else if (right == null || index >= String.valueOf(right).length()) { + if (index < 0 || index >= stringValueOfRight.length()) { return ""; } - return String.valueOf(String.valueOf(right).charAt(index)); + return String.valueOf(stringValueOfRight.charAt(index)); } - private Object interpretFunctionMod(Object left, Object right) { - - double dividend = (Double) left; - double divisor = (Double) right; - - if (dividend == 0 || divisor == 0) { - return dividend; - } - - if (divisor > 0) { - while (dividend < 0) { - dividend += java.lang.Math.abs(divisor); - } - } else { - if (dividend > 0) { - return (dividend % divisor) + divisor; - } - } - - return dividend % divisor; - } - - private Object interpretFunctionRand(Object left, Object right) { - double from = (double) left; - double to = (double) right; + private double interpretFunctionRand(double from, double to) { double low = Math.min(from, to); double high = Math.max(from, to); @@ -775,7 +577,7 @@ private Object interpretFunctionRand(Object left, Object right) { if (isInteger(low) && isInteger(high) && !isNumberWithDecimalPoint(leftChild) && !isNumberWithDecimalPoint(rightChild)) { - return low + Math.floor(Math.random() * ((high + 1) - low)); + return Math.floor(Math.random() * ((high + 1) - low)) + low; } else { return (Math.random() * (high - low)) + low; } @@ -785,235 +587,66 @@ private static boolean isNumberWithDecimalPoint(FormulaElement element) { return element.type == ElementType.NUMBER && element.value.contains("."); } - private Object interpretOperator(Operators operator, Sprite sprite) { - - if (leftChild != null) { // binary operator - Object leftObject; - Object rightObject; - try { - leftObject = leftChild.interpretRecursive(sprite); - } catch (NumberFormatException numberFormatException) { - leftObject = Double.NaN; - } - - try { - rightObject = rightChild.interpretRecursive(sprite); - } catch (NumberFormatException numberFormatException) { - rightObject = Double.NaN; - } - - Double left; - Double right; - - switch (operator) { - case PLUS: - left = interpretOperator(leftObject); - right = interpretOperator(rightObject); - return left + right; - case MINUS: - left = interpretOperator(leftObject); - right = interpretOperator(rightObject); - return left - right; - case MULT: - left = interpretOperator(leftObject); - right = interpretOperator(rightObject); - return left * right; - case DIVIDE: - left = interpretOperator(leftObject); - right = interpretOperator(rightObject); - return left / right; - case POW: - left = interpretOperator(leftObject); - right = interpretOperator(rightObject); - return java.lang.Math.pow(left, right); - case EQUAL: - return interpretOperatorEqual(leftObject, rightObject); - case NOT_EQUAL: - return interpretOperatorEqual(leftObject, rightObject) == 1d ? 0d : 1d; - case GREATER_THAN: - left = interpretOperator(leftObject); - right = interpretOperator(rightObject); - return left.compareTo(right) > 0 ? 1d : 0d; - case GREATER_OR_EQUAL: - left = interpretOperator(leftObject); - right = interpretOperator(rightObject); - return left.compareTo(right) >= 0 ? 1d : 0d; - case SMALLER_THAN: - left = interpretOperator(leftObject); - right = interpretOperator(rightObject); - return left.compareTo(right) < 0 ? 1d : 0d; - case SMALLER_OR_EQUAL: - left = interpretOperator(leftObject); - right = interpretOperator(rightObject); - return left.compareTo(right) <= 0 ? 1d : 0d; - case LOGICAL_AND: - left = interpretOperator(leftObject); - right = interpretOperator(rightObject); - return (left * right) != 0d ? 1d : 0d; - case LOGICAL_OR: - left = interpretOperator(leftObject); - right = interpretOperator(rightObject); - return left != 0d || right != 0d ? 1d : 0d; - } - } else { //unary operators - Object rightObject; - try { - rightObject = rightChild.interpretRecursive(sprite); - } catch (NumberFormatException numberFormatException) { - rightObject = Double.NaN; - } - - switch (operator) { - case MINUS: - Double result = interpretOperator(rightObject); - return -result; - case LOGICAL_NOT: - return interpretOperator(rightObject) == 0d ? 1d : 0d; - } - } - return 0d; - } - - private Object interpretObjectSensor(Sensors sensor, Sprite sprite) { - Object returnValue = 0d; - LookData lookData = sprite.look.getLookData(); - List lookDataList = sprite.getLookList(); - if (lookData == null && lookDataList.size() > 0) { - lookData = lookDataList.get(0); - } - switch (sensor) { - case OBJECT_BRIGHTNESS: - returnValue = (double) sprite.look.getBrightnessInUserInterfaceDimensionUnit(); - break; - case OBJECT_COLOR: - returnValue = (double) sprite.look.getColorInUserInterfaceDimensionUnit(); - break; - case OBJECT_TRANSPARENCY: - returnValue = (double) sprite.look.getTransparencyInUserInterfaceDimensionUnit(); - break; - case OBJECT_LAYER: - if (sprite.look.getZIndex() < 0) { - returnValue = (double) ProjectManager.getInstance().getCurrentlyEditedScene() - .getSpriteList().indexOf(sprite); - } else if (sprite.look.getZIndex() == 0) { - returnValue = 0d; - } else { - returnValue = (double) sprite.look.getZIndex() - Constants.Z_INDEX_NUMBER_VIRTUAL_LAYERS; - } - break; - case OBJECT_ROTATION: - returnValue = (double) sprite.look.getDirectionInUserInterfaceDimensionUnit(); - break; - case OBJECT_SIZE: - returnValue = (double) sprite.look.getSizeInUserInterfaceDimensionUnit(); - break; - case OBJECT_X: - returnValue = (double) sprite.look.getXInUserInterfaceDimensionUnit(); - break; - case OBJECT_Y: - returnValue = (double) sprite.look.getYInUserInterfaceDimensionUnit(); - break; - case OBJECT_ANGULAR_VELOCITY: - returnValue = (double) sprite.look.getAngularVelocityInUserInterfaceDimensionUnit(); - break; - case OBJECT_X_VELOCITY: - returnValue = (double) sprite.look.getXVelocityInUserInterfaceDimensionUnit(); - break; - case OBJECT_Y_VELOCITY: - returnValue = (double) sprite.look.getYVelocityInUserInterfaceDimensionUnit(); - break; - case OBJECT_LOOK_NUMBER: - case OBJECT_BACKGROUND_NUMBER: - returnValue = 1.0d + ((lookData != null) ? lookDataList.indexOf(lookData) : 0); - break; - case OBJECT_LOOK_NAME: - case OBJECT_BACKGROUND_NAME: - returnValue = (lookData != null) ? lookData.getName() : ""; - break; - case OBJECT_DISTANCE_TO: - returnValue = (double) sprite.look.getDistanceToTouchPositionInUserInterfaceDimensions(); - break; - case NFC_TAG_MESSAGE: - returnValue = NfcHandler.getLastNfcTagMessage(); - break; - case NFC_TAG_ID: - returnValue = NfcHandler.getLastNfcTagId(); - break; - case COLLIDES_WITH_EDGE: - if (StageActivity.stageListener != null) { - returnValue = StageActivity.stageListener.firstFrameDrawn ? CollisionDetection.collidesWithEdge(sprite - .look) : 0d; - } else { - returnValue = 0d; - } - break; - case COLLIDES_WITH_FINGER: - returnValue = CollisionDetection.collidesWithFinger(sprite.look); - break; - } - return returnValue; - } - - private Double interpretOperatorEqual(Object left, Object right) { - try { - Double tempLeft = Double.valueOf(String.valueOf(left)); - Double tempRight = Double.valueOf(String.valueOf(right)); - int compareResult = getCompareResult(tempLeft, tempRight); - if (compareResult == 0) { - return 1d; - } - return 0d; - } catch (NumberFormatException numberFormatException) { - int compareResult = String.valueOf(left).compareTo(String.valueOf(right)); - if (compareResult == 0) { - return 1d; - } - return 0d; - } - } - - private int getCompareResult(Double left, Double right) { - int compareResult; - if (left == 0 || right == 0) { - compareResult = ((Double) Math.abs(left)).compareTo(Math.abs(right)); - } else { - compareResult = left.compareTo(right); - } - return compareResult; - } - - private Double interpretOperator(Object object) { - if (object instanceof String) { - try { - return Double.valueOf((String) object); - } catch (NumberFormatException numberFormatException) { - return Double.NaN; - } + private double interpretOperator(@NotNull Operators operator, Sprite sprite) { + if (leftChild != null) { + return interpretBinaryOperator(operator, sprite); } else { - return (Double) object; + return interpretUnaryOperator(operator, sprite); + } + } + + private double interpretUnaryOperator(@NotNull Operators operator, Sprite sprite) { + Object rightObject = tryInterpretElementRecursive(rightChild, sprite); + double right = tryInterpretDoubleValue(rightObject); + + switch (operator) { + case MINUS: + return -right; + case LOGICAL_NOT: + return booleanToDouble(right == FALSE); + default: + return FALSE; + } + } + + private double interpretBinaryOperator(@NotNull Operators operator, Sprite sprite) { + Object leftObject = tryInterpretElementRecursive(leftChild, sprite); + Object rightObject = tryInterpretElementRecursive(rightChild, sprite); + Double left = tryInterpretDoubleValue(leftObject); + Double right = tryInterpretDoubleValue(rightObject); + + switch (operator) { + case PLUS: + return left + right; + case MINUS: + return left - right; + case MULT: + return left * right; + case DIVIDE: + return left / right; + case POW: + return Math.pow(left, right); + case EQUAL: + return booleanToDouble(interpretOperatorEqual(leftObject, rightObject)); + case NOT_EQUAL: + return booleanToDouble(!(interpretOperatorEqual(leftObject, rightObject))); + case GREATER_THAN: + return booleanToDouble(left.compareTo(right) > 0); + case GREATER_OR_EQUAL: + return booleanToDouble(left.compareTo(right) >= 0); + case SMALLER_THAN: + return booleanToDouble(left.compareTo(right) < 0); + case SMALLER_OR_EQUAL: + return booleanToDouble(left.compareTo(right) <= 0); + case LOGICAL_AND: + return booleanToDouble(left != FALSE && right != FALSE); + case LOGICAL_OR: + return booleanToDouble(left != FALSE || right != FALSE); + default: + return FALSE; } } - private Object normalizeDegeneratedDoubleValues(Object valueToCheck) { - - if (valueToCheck instanceof String || valueToCheck instanceof Character) { - return valueToCheck; - } - - if (valueToCheck == null) { - return 0.0; - } - - if ((Double) valueToCheck == Double.NEGATIVE_INFINITY) { - return -Double.MAX_VALUE; - } - if ((Double) valueToCheck == Double.POSITIVE_INFINITY) { - return Double.MAX_VALUE; - } - - return valueToCheck; - } - public FormulaElement getParent() { return parent; } @@ -1056,10 +689,6 @@ public void replaceWithSubElement(String operator, FormulaElement rightChild) { cloneThis.parent.rightChild = cloneThis; } - private boolean isInteger(double value) { - return ((Math.abs(value) - (int) Math.abs(value)) < Double.MIN_VALUE); - } - public boolean isLogicalOperator() { return (type == ElementType.OPERATOR) && Operators.getOperatorByValue(value).isLogicalOperator; } @@ -1070,42 +699,7 @@ public boolean containsElement(ElementType elementType) { || (rightChild != null && rightChild.containsElement(elementType))); } - public boolean isUserVariableWithTypeString(Sprite sprite) { - if (type == ElementType.USER_VARIABLE) { - Project currentProject = ProjectManager.getInstance().getCurrentProject(); - UserVariable userVariable = UserDataWrapper.getUserVariable(value, sprite, currentProject); - Object userVariableValue = userVariable.getValue(); - return userVariableValue instanceof String; - } - return false; - } - - private int handleLengthUserVariableParameter(Sprite sprite) { - Project currentProject = ProjectManager.getInstance().getCurrentProject(); - UserVariable userVariable = UserDataWrapper.getUserVariable(leftChild.value, sprite, currentProject); - Object userVariableValue = userVariable.getValue(); - if (userVariableValue instanceof String) { - return String.valueOf(userVariableValue).length(); - } else { - if (isInteger((Double) userVariableValue)) { - return Integer.toString(((Double) userVariableValue).intValue()).length(); - } else { - return Double.toString(((Double) userVariableValue)).length(); - } - } - } - - private int handleNumberOfItemsOfUserListParameter(Sprite sprite) { - Project currentProject = ProjectManager.getInstance().getCurrentProject(); - UserList userList = UserDataWrapper.getUserList(leftChild.value, sprite, currentProject); - if (userList == null) { - return 0; - } - - return userList.getValue().size(); - } - - boolean isNumber() { + public boolean isNumber() { if (type == ElementType.OPERATOR) { Operators operator = Operators.getOperatorByValue(value); return (operator == Operators.MINUS) && (leftChild == null) && rightChild.isNumber(); @@ -1115,127 +709,37 @@ boolean isNumber() { @Override public FormulaElement clone() { - FormulaElement leftChildClone = leftChild == null ? null : leftChild.clone(); - FormulaElement rightChildClone = rightChild == null ? null : rightChild.clone(); - return new FormulaElement(type, value == null ? "" : value, null, leftChildClone, rightChildClone); + FormulaElement leftChildClone = tryCloneElement(leftChild); + FormulaElement rightChildClone = tryCloneElement(rightChild); + String valueClone = value == null ? "" : value; + return new FormulaElement(type, valueClone, null, leftChildClone, rightChildClone); + } + + private FormulaElement tryCloneElement(FormulaElement element) { + return element == null ? null : element.clone(); } public void addRequiredResources(final Set requiredResourcesSet) { - if (leftChild != null) { - leftChild.addRequiredResources(requiredResourcesSet); - } - if (rightChild != null) { - rightChild.addRequiredResources(requiredResourcesSet); - } - if (type == ElementType.FUNCTION) { - Functions functions = Functions.getFunctionByValue(value); - switch (functions) { - case ARDUINOANALOG: - case ARDUINODIGITAL: - requiredResourcesSet.add(Brick.BLUETOOTH_SENSORS_ARDUINO); - break; - case RASPIDIGITAL: - requiredResourcesSet.add(Brick.SOCKET_RASPI); - break; - } - } - if (type == ElementType.SENSOR) { - Sensors sensor = Sensors.getSensorByValue(value); - switch (sensor) { - case X_ACCELERATION: - case Y_ACCELERATION: - case Z_ACCELERATION: - requiredResourcesSet.add(Brick.SENSOR_ACCELERATION); - break; - - case X_INCLINATION: - case Y_INCLINATION: - requiredResourcesSet.add(Brick.SENSOR_INCLINATION); - break; - - case COMPASS_DIRECTION: - requiredResourcesSet.add(Brick.SENSOR_COMPASS); - break; - - case LATITUDE: - case LONGITUDE: - case LOCATION_ACCURACY: - case ALTITUDE: - requiredResourcesSet.add(Brick.SENSOR_GPS); - break; - - case FACE_DETECTED: - case FACE_SIZE: - case FACE_X_POSITION: - case FACE_Y_POSITION: - requiredResourcesSet.add(Brick.FACE_DETECTION); - break; - - case NXT_SENSOR_1: - case NXT_SENSOR_2: - case NXT_SENSOR_3: - case NXT_SENSOR_4: - requiredResourcesSet.add(Brick.BLUETOOTH_LEGO_NXT); - break; - - case EV3_SENSOR_1: - case EV3_SENSOR_2: - case EV3_SENSOR_3: - case EV3_SENSOR_4: - requiredResourcesSet.add(Brick.BLUETOOTH_LEGO_EV3); - break; - - case PHIRO_FRONT_LEFT: - case PHIRO_FRONT_RIGHT: - case PHIRO_SIDE_LEFT: - case PHIRO_SIDE_RIGHT: - case PHIRO_BOTTOM_LEFT: - case PHIRO_BOTTOM_RIGHT: - requiredResourcesSet.add(Brick.BLUETOOTH_PHIRO); - break; - - case DRONE_BATTERY_STATUS: - case DRONE_CAMERA_READY: - case DRONE_EMERGENCY_STATE: - case DRONE_FLYING: - case DRONE_INITIALIZED: - case DRONE_NUM_FRAMES: - case DRONE_RECORD_READY: - case DRONE_RECORDING: - case DRONE_USB_ACTIVE: - case DRONE_USB_REMAINING_TIME: - requiredResourcesSet.add(Brick.ARDRONE_SUPPORT); - break; - - case NFC_TAG_MESSAGE: - case NFC_TAG_ID: - requiredResourcesSet.add(Brick.NFC_ADAPTER); - break; - - case COLLIDES_WITH_EDGE: - requiredResourcesSet.add(Brick.COLLISION); - break; - case COLLIDES_WITH_FINGER: - requiredResourcesSet.add(Brick.COLLISION); - break; - - case GAMEPAD_A_PRESSED: - case GAMEPAD_B_PRESSED: - case GAMEPAD_DOWN_PRESSED: - case GAMEPAD_UP_PRESSED: - case GAMEPAD_LEFT_PRESSED: - case GAMEPAD_RIGHT_PRESSED: - requiredResourcesSet.add(Brick.CAST_REQUIRED); - break; - - case LOUDNESS: - requiredResourcesSet.add(Brick.MICROPHONE); - break; - default: - } + tryAddRequiredResources(requiredResourcesSet, leftChild); + tryAddRequiredResources(requiredResourcesSet, rightChild); + + switch (type) { + case FUNCTION: + addFunctionResources(requiredResourcesSet, Functions.getFunctionByValue(value)); + break; + case SENSOR: + addSensorsResources(requiredResourcesSet, Sensors.getSensorByValue(value)); + break; + case COLLISION_FORMULA: + requiredResourcesSet.add(Brick.COLLISION); + break; + default: } - if (type == ElementType.COLLISION_FORMULA) { - requiredResourcesSet.add(Brick.COLLISION); + } + + private void tryAddRequiredResources(Set resourceSet, FormulaElement element) { + if (element != null) { + element.addRequiredResources(resourceSet); } } } diff --git a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/Functions.java b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/Functions.java index 02ac34cf2b1..c0a449793ee 100644 --- a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/Functions.java +++ b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/Functions.java @@ -22,7 +22,7 @@ */ package org.catrobat.catroid.formulaeditor; -import android.util.Log; +import org.catrobat.catroid.utils.EnumUtils; import java.util.EnumSet; @@ -41,15 +41,10 @@ public enum Functions { NUMBER_OF_ITEMS); public static boolean isFunction(String value) { - return getFunctionByValue(value) != null; + return EnumUtils.isValidEnum(Functions.class, value); } public static Functions getFunctionByValue(String value) { - try { - return valueOf(value); - } catch (IllegalArgumentException illegalArgumentException) { - Log.e(TAG, Log.getStackTraceString(illegalArgumentException)); - } - return null; + return EnumUtils.getEnum(Functions.class, value); } } diff --git a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/InternToken.java b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/InternToken.java index 9d751bd9eee..c6dfd7887a7 100644 --- a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/InternToken.java +++ b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/InternToken.java @@ -22,6 +22,8 @@ */ package org.catrobat.catroid.formulaeditor; +import org.catrobat.catroid.ProjectManager; +import org.catrobat.catroid.content.Project; import org.catrobat.catroid.sensing.CollisionDetection; import java.util.List; @@ -68,7 +70,8 @@ public void updateCollisionFormula(String oldName, String newName) { public void updateCollisionFormulaToVersion() { if (internTokenType == InternTokenType.COLLISION_FORMULA) { - String secondSpriteName = CollisionDetection.getSecondSpriteNameFromCollisionFormulaString(tokenStringValue); + Project currentProject = ProjectManager.getInstance().getCurrentProject(); + String secondSpriteName = CollisionDetection.getSecondSpriteNameFromCollisionFormulaString(tokenStringValue, currentProject); if (secondSpriteName != null) { tokenStringValue = secondSpriteName; } diff --git a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/Operators.java b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/Operators.java index 50b077482af..c9ce2545389 100644 --- a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/Operators.java +++ b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/Operators.java @@ -22,7 +22,7 @@ */ package org.catrobat.catroid.formulaeditor; -import android.util.Log; +import org.catrobat.catroid.utils.EnumUtils; public enum Operators { LOGICAL_AND(2, true), LOGICAL_OR(1, true), EQUAL(3, true), NOT_EQUAL(4, true), SMALLER_OR_EQUAL(4, true), @@ -45,28 +45,14 @@ public enum Operators { } public int compareOperatorTo(Operators operator) { - int returnValue = 0; - if (priority > operator.priority) { - returnValue = 1; - } else if (priority == operator.priority) { - returnValue = 0; - } else if (priority < operator.priority) { - returnValue = -1; - } - - return returnValue; + return Integer.compare(priority, operator.priority); } public static Operators getOperatorByValue(String value) { - try { - return valueOf(value); - } catch (IllegalArgumentException illegalArgumentException) { - Log.e(TAG, Log.getStackTraceString(illegalArgumentException)); - } - return null; + return EnumUtils.getEnum(Operators.class, value); } public static boolean isOperator(String value) { - return getOperatorByValue(value) != null; + return EnumUtils.isValidEnum(Operators.class, value); } } diff --git a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/Sensors.java b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/Sensors.java index 22071399f65..199c7d1fe21 100644 --- a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/Sensors.java +++ b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/Sensors.java @@ -22,7 +22,7 @@ */ package org.catrobat.catroid.formulaeditor; -import android.util.Log; +import org.catrobat.catroid.utils.EnumUtils; public enum Sensors { X_ACCELERATION, Y_ACCELERATION, Z_ACCELERATION, COMPASS_DIRECTION, X_INCLINATION, Y_INCLINATION, LOUDNESS, @@ -55,15 +55,10 @@ LAST_FINGER_INDEX, FINGER_X, FINGER_Y, FINGER_TOUCHED, OBJECT_LOOK_NUMBER(true), } public static boolean isSensor(String value) { - return getSensorByValue(value) != null; + return EnumUtils.isValidEnum(Sensors.class, value); } public static Sensors getSensorByValue(String value) { - try { - return valueOf(value); - } catch (IllegalArgumentException illegalArgumentException) { - Log.e(TAG, Log.getStackTraceString(illegalArgumentException)); - } - return null; + return EnumUtils.getEnum(Sensors.class, value); } } diff --git a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/common/Conversions.kt b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/common/Conversions.kt new file mode 100644 index 00000000000..a75964958ab --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/common/Conversions.kt @@ -0,0 +1,64 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2019 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.formulaeditor.common + +import android.graphics.Color +import androidx.annotation.ColorInt + +object Conversions { + const val TRUE = 1.0 + const val FALSE = 0.0 + + private fun tryParseDouble(argument: String): Double? { + return try { + argument.toDouble() + } catch (numberFormatException: NumberFormatException) { + null + } + } + + @Suppress("MagicNumber") + @ColorInt + @JvmStatic + @JvmOverloads + fun tryParseColor(string: String?, defaultValue: Int = Color.BLACK): Int { + return if (string != null && string.length == 7 && string.matches("^#[0-9a-fA-F]+$".toRegex())) { + Color.parseColor(string) + } else { + defaultValue + } + } + + @JvmStatic + fun convertArgumentToDouble(argument: Any?): Double? { + return argument?.let { + when (argument) { + is String -> tryParseDouble(argument) + else -> argument as Double? + } + } + } + + @JvmStatic + fun booleanToDouble(value: Boolean) = if (value) TRUE else FALSE +} diff --git a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/common/FormulaElementOperations.kt b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/common/FormulaElementOperations.kt new file mode 100644 index 00000000000..be2949bef4f --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/common/FormulaElementOperations.kt @@ -0,0 +1,308 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2020 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.formulaeditor.common + +import android.content.res.Resources +import com.badlogic.gdx.math.Rectangle +import org.catrobat.catroid.common.Constants +import org.catrobat.catroid.common.LookData +import org.catrobat.catroid.content.GroupSprite +import org.catrobat.catroid.content.Look +import org.catrobat.catroid.content.Project +import org.catrobat.catroid.content.Scene +import org.catrobat.catroid.content.Sprite +import org.catrobat.catroid.formulaeditor.FormulaElement +import org.catrobat.catroid.formulaeditor.SensorHandler +import org.catrobat.catroid.formulaeditor.Sensors +import org.catrobat.catroid.formulaeditor.UserList +import org.catrobat.catroid.formulaeditor.UserVariable +import org.catrobat.catroid.nfc.NfcHandler +import org.catrobat.catroid.sensing.CollisionDetection +import org.catrobat.catroid.stage.StageActivity +import org.catrobat.catroid.stage.StageListener +import org.catrobat.catroid.utils.NumberFormats +import org.catrobat.catroid.utils.TouchUtil +import java.lang.Double.valueOf +import kotlin.math.round + +object FormulaElementOperations { + @JvmStatic + fun getLookLayerIndex(sprite: Sprite?, look: Look, spriteList: List): Double { + val lookZIndex = look.zIndex + return when { + lookZIndex == 0 -> 0.0 + lookZIndex < 0 -> spriteList.indexOf(sprite).toDouble() + else -> lookZIndex.toDouble() - Constants.Z_INDEX_NUMBER_VIRTUAL_LAYERS + } + } + + @JvmStatic + fun equalsDoubleIEEE754(left: Double, right: Double) = + left.isNaN() && right.isNaN() || !left.isNaN() && !right.isNaN() && left >= right && left <= right + + @JvmStatic + fun interpretOperatorEqual(left: Any, right: Any): Boolean { + val leftString = left.toString() + val rightString = right.toString() + return try { + equalsDoubleIEEE754(leftString.toDouble(), rightString.toDouble()) + } catch (_: NumberFormatException) { + leftString == rightString + } + } + + @JvmStatic + fun tryInterpretDoubleValue(obj: Any): Double { + return when (obj) { + is String -> try { + valueOf(obj) + } catch (_: NumberFormatException) { + Double.NaN + } + else -> obj as Double + } + } + + @JvmStatic + fun normalizeDegeneratedDoubleValues(value: Any?): Any { + return when (value) { + is String, + is Char -> value + is Double -> when (value) { + Double.NEGATIVE_INFINITY -> -Double.MAX_VALUE + Double.POSITIVE_INFINITY -> Double.MAX_VALUE + else -> value + } + else -> 0.0 + } + } + + @JvmStatic + fun isInteger(value: Double) = value.isFinite() && value == round(value) + + @JvmStatic + fun tryGetLookNumber(lookData: LookData?, lookDataList: List) = + lookData?.let { lookDataList.indexOf(it) + 1.0 } ?: 1.0 + + @JvmStatic + fun getLookName(lookData: LookData?) = lookData?.name ?: "" + + @JvmStatic + fun tryCalculateCollidesWithEdge( + look: Look, + stageListener: StageListener?, + screen: Rectangle? + ): Double { + return if (stageListener?.firstFrameDrawn == true) { + Conversions.booleanToDouble( + CollisionDetection.collidesWithEdge(look.currentCollisionPolygon, screen) + ) + } else { + Conversions.FALSE + } + } + + @JvmStatic + fun calculateCollidesWithFinger(look: Look): Double { + return CollisionDetection.collidesWithFinger( + look.currentCollisionPolygon, + TouchUtil.getCurrentTouchingPoints() + ) + } + + @JvmStatic + fun interpretObjectSensor( + sensor: Sensors?, + sprite: Sprite, + currentlyEditedScene: Scene, + currentProject: Project + ): Any { + val look = sprite.look + val lookDataList = sprite.lookList + val lookData = look.lookData ?: if (lookDataList.isNotEmpty()) lookDataList[0] else null + + return when (sensor) { + Sensors.OBJECT_BRIGHTNESS -> look.brightnessInUserInterfaceDimensionUnit.toDouble() + Sensors.OBJECT_COLOR -> look.colorInUserInterfaceDimensionUnit.toDouble() + Sensors.OBJECT_TRANSPARENCY -> look.transparencyInUserInterfaceDimensionUnit.toDouble() + Sensors.OBJECT_LAYER -> getLookLayerIndex(sprite, look, currentlyEditedScene.spriteList) + Sensors.OBJECT_ROTATION -> look.directionInUserInterfaceDimensionUnit.toDouble() + Sensors.OBJECT_SIZE -> look.sizeInUserInterfaceDimensionUnit.toDouble() + Sensors.OBJECT_X -> look.xInUserInterfaceDimensionUnit.toDouble() + Sensors.OBJECT_Y -> look.yInUserInterfaceDimensionUnit.toDouble() + Sensors.OBJECT_ANGULAR_VELOCITY -> look.angularVelocityInUserInterfaceDimensionUnit.toDouble() + Sensors.OBJECT_X_VELOCITY -> look.xVelocityInUserInterfaceDimensionUnit.toDouble() + Sensors.OBJECT_Y_VELOCITY -> look.yVelocityInUserInterfaceDimensionUnit.toDouble() + Sensors.OBJECT_DISTANCE_TO -> look.distanceToTouchPositionInUserInterfaceDimensions.toDouble() + Sensors.OBJECT_LOOK_NUMBER, + Sensors.OBJECT_BACKGROUND_NUMBER -> tryGetLookNumber(lookData, lookDataList) + Sensors.OBJECT_LOOK_NAME, + Sensors.OBJECT_BACKGROUND_NAME -> getLookName(lookData) + Sensors.NFC_TAG_MESSAGE -> NfcHandler.getLastNfcTagMessage() + Sensors.NFC_TAG_ID -> NfcHandler.getLastNfcTagId() + Sensors.COLLIDES_WITH_EDGE -> tryCalculateCollidesWithEdge( + look, + StageActivity.stageListener, + currentProject.screenRectangle + ) + Sensors.COLLIDES_WITH_FINGER -> calculateCollidesWithFinger(look) + else -> Conversions.FALSE + } + } + + @JvmStatic + fun getAllClones(sprite: Sprite, stageListener: StageListener?): List { + return stageListener?.let { + listOf(sprite) + it.getAllClonesOfSprite(sprite) + } ?: listOf(sprite) + } + + @JvmStatic + fun interpretUserList(userList: UserList?): Any { + return userList?.let { + when { + it.value.isEmpty() -> "" + it.value.size == 1 -> it.value[0] + else -> interpretMultipleItemsUserList(it.value) + } + } ?: Conversions.FALSE + } + + private fun interpretMultipleItemsUserList(userListValues: List): Any { + val userListStringValues = userListValues.filter { it is Double || it is String }.map { + NumberFormats.trimTrailingCharacters( + when (it) { + is Double -> it.toInt().toString() + else -> it as String + } + ) + } + val stringBuilder = StringBuilder(userListStringValues.size) + val separator = if (listConsistsOfSingleCharacters(userListStringValues)) "" else " " + for (userListStringValue in userListStringValues) { + stringBuilder.append(NumberFormats.trimTrailingCharacters(userListStringValue)) + stringBuilder.append(separator) + } + return stringBuilder.toString().trim { it <= ' ' } + } + + private fun listConsistsOfSingleCharacters(userListStringValues: List) = userListStringValues.none { it.length > 1 } + + @JvmStatic + fun interpretUserVariable(userVariable: UserVariable?) = userVariable?.value ?: Conversions.FALSE + + @JvmStatic + fun interpretLookCollision(look: Look, looks: List): Double { + val collides = looks.any { + look != it && CollisionDetection.checkCollisionBetweenLooks(look, it) + } + return Conversions.booleanToDouble(collides) + } + + @JvmStatic + fun toLooks(sprites: List) = sprites.map { it.look } + + @JvmStatic + fun interpretSensor( + sprite: Sprite, + currentlyEditedScene: Scene, + currentProject: Project, + value: String? + ): Any { + val sensor = Sensors.getSensorByValue(value) + return if (sensor.isObjectSensor) { + interpretObjectSensor(sensor, sprite, currentlyEditedScene, currentProject) + } else { + SensorHandler.getSensorValue(sensor) + } + } + + @JvmStatic + fun tryFindSprite(scene: Scene, spriteName: String?): Sprite? { + return try { + scene.getSprite(spriteName) + } catch (_: Resources.NotFoundException) { + null + } + } + + @JvmStatic + fun interpretCollision( + firstLook: Look, + secondSpriteName: String?, + currentlyPlayingScene: Scene, + stageListener: StageListener? + ): Double { + val secondSprite = + tryFindSprite(currentlyPlayingScene, secondSpriteName) ?: return Conversions.FALSE + val sprites = when (secondSprite) { + is GroupSprite -> GroupSprite.getSpritesFromGroupWithGroupName( + secondSpriteName, + currentlyPlayingScene.spriteList + ) + else -> getAllClones(secondSprite, stageListener) + } + return interpretLookCollision(firstLook, toLooks(sprites)) + } + + @Suppress("TooGenericExceptionCaught") + @JvmStatic + fun tryInterpretCollision( + firstLook: Look, + secondSpriteName: String?, + currentlyPlayingScene: Scene, + stageListener: StageListener? + ): Double { + return try { + interpretCollision(firstLook, secondSpriteName, currentlyPlayingScene, stageListener) + } catch (_: Exception) { + Conversions.FALSE + } + } + + @JvmStatic + fun tryInterpretElementRecursive(element: FormulaElement, sprite: Sprite?): Any { + return try { + element.interpretRecursive(sprite) + } catch (numberFormatException: NumberFormatException) { + Double.NaN + } + } + + @JvmStatic + fun tryParseIntFromObject(value: Any): Int { + return when (value) { + is String -> tryParseIntFromString(value) + else -> (value as Double).toInt() + } + } + + @JvmStatic + private fun tryParseIntFromString(value: String): Int { + return try { + valueOf(value).toInt() + } catch (_: NumberFormatException) { + 0 + } + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/common/FormulaElementResources.kt b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/common/FormulaElementResources.kt new file mode 100644 index 00000000000..65d68a08f6c --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/common/FormulaElementResources.kt @@ -0,0 +1,107 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2020 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.formulaeditor.common + +import org.catrobat.catroid.content.bricks.Brick +import org.catrobat.catroid.formulaeditor.Functions +import org.catrobat.catroid.formulaeditor.Sensors + +object FormulaElementResources { + @JvmStatic + fun addSensorsResources(resources: MutableSet, sensor: Sensors?) { + when (sensor) { + Sensors.X_ACCELERATION, + Sensors.Y_ACCELERATION, + Sensors.Z_ACCELERATION -> Brick.SENSOR_ACCELERATION + + Sensors.X_INCLINATION, + Sensors.Y_INCLINATION -> Brick.SENSOR_INCLINATION + + Sensors.COMPASS_DIRECTION -> Brick.SENSOR_COMPASS + + Sensors.LATITUDE, + Sensors.LONGITUDE, + Sensors.LOCATION_ACCURACY, + Sensors.ALTITUDE -> Brick.SENSOR_GPS + + Sensors.FACE_DETECTED, + Sensors.FACE_SIZE, + Sensors.FACE_X_POSITION, + Sensors.FACE_Y_POSITION -> Brick.FACE_DETECTION + + Sensors.NXT_SENSOR_1, + Sensors.NXT_SENSOR_2, + Sensors.NXT_SENSOR_3, + Sensors.NXT_SENSOR_4 -> Brick.BLUETOOTH_LEGO_NXT + + Sensors.EV3_SENSOR_1, + Sensors.EV3_SENSOR_2, + Sensors.EV3_SENSOR_3, + Sensors.EV3_SENSOR_4 -> Brick.BLUETOOTH_LEGO_EV3 + + Sensors.PHIRO_FRONT_LEFT, + Sensors.PHIRO_FRONT_RIGHT, + Sensors.PHIRO_SIDE_LEFT, + Sensors.PHIRO_SIDE_RIGHT, + Sensors.PHIRO_BOTTOM_LEFT, + Sensors.PHIRO_BOTTOM_RIGHT -> Brick.BLUETOOTH_PHIRO + + Sensors.DRONE_BATTERY_STATUS, + Sensors.DRONE_CAMERA_READY, + Sensors.DRONE_EMERGENCY_STATE, + Sensors.DRONE_FLYING, + Sensors.DRONE_INITIALIZED, + Sensors.DRONE_NUM_FRAMES, + Sensors.DRONE_RECORD_READY, + Sensors.DRONE_RECORDING, + Sensors.DRONE_USB_ACTIVE, + Sensors.DRONE_USB_REMAINING_TIME -> Brick.ARDRONE_SUPPORT + + Sensors.NFC_TAG_MESSAGE, + Sensors.NFC_TAG_ID -> Brick.NFC_ADAPTER + + Sensors.COLLIDES_WITH_EDGE, + Sensors.COLLIDES_WITH_FINGER -> Brick.COLLISION + + Sensors.GAMEPAD_A_PRESSED, + Sensors.GAMEPAD_B_PRESSED, + Sensors.GAMEPAD_DOWN_PRESSED, + Sensors.GAMEPAD_UP_PRESSED, + Sensors.GAMEPAD_LEFT_PRESSED, + Sensors.GAMEPAD_RIGHT_PRESSED -> Brick.CAST_REQUIRED + + Sensors.LOUDNESS -> Brick.MICROPHONE + + else -> return + }.let { resources.add(it) } + } + + @JvmStatic + fun addFunctionResources(resources: MutableSet, functions: Functions?) { + when (functions) { + Functions.ARDUINOANALOG, Functions.ARDUINODIGITAL -> Brick.BLUETOOTH_SENSORS_ARDUINO + Functions.RASPIDIGITAL -> Brick.SOCKET_RASPI + else -> return + }.let { resources.add(it) } + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/ArduinoFunctionProvider.java b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/ArduinoFunctionProvider.java new file mode 100644 index 00000000000..428982c06c0 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/ArduinoFunctionProvider.java @@ -0,0 +1,60 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2019 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.formulaeditor.function; + +import org.catrobat.catroid.bluetooth.base.BluetoothDevice; +import org.catrobat.catroid.common.CatroidService; +import org.catrobat.catroid.common.ServiceProvider; +import org.catrobat.catroid.devices.arduino.Arduino; +import org.catrobat.catroid.formulaeditor.Functions; + +import java.util.Map; + +public class ArduinoFunctionProvider implements FunctionProvider { + @Override + public void addFunctionsToMap(Map formulaFunctions) { + formulaFunctions.put(Functions.ARDUINODIGITAL, new UnaryFunction(this::interpretFunctionArduinoDigital)); + formulaFunctions.put(Functions.ARDUINOANALOG, new UnaryFunction(this::interpretFunctionArduinoAnalog)); + } + + private double interpretFunctionArduinoAnalog(double argument) { + Arduino arduino = getArduino(); + if (arduino == null || argument < 0 || argument > 5) { + return 0; + } + return arduino.getAnalogArduinoPin((int) argument); + } + + private double interpretFunctionArduinoDigital(double argument) { + Arduino arduino = getArduino(); + if (arduino == null || argument < 0 || argument > 13) { + return 0; + } + return arduino.getDigitalArduinoPin((int) argument); + } + + private Arduino getArduino() { + return ServiceProvider.getService(CatroidService.BLUETOOTH_DEVICE_SERVICE).getDevice(BluetoothDevice.ARDUINO); + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/BinaryFunction.java b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/BinaryFunction.java new file mode 100644 index 00000000000..463e2fec415 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/BinaryFunction.java @@ -0,0 +1,50 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2019 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.formulaeditor.function; + +public class BinaryFunction implements FormulaFunction, BinaryFunctionAction { + private final BinaryFunctionAction action; + + public BinaryFunction(BinaryFunctionAction action) { + this.action = action; + } + + @Override + public Double execute(Double firstArgument, Double secondArgument) { + if (firstArgument == null || secondArgument == null) { + return 0d; + } else { + return action.execute(firstArgument, secondArgument); + } + } + + @Override + public Double execute(Double... args) { + if (args == null || args.length < 2) { + return 0d; + } else { + return execute(args[0], args[1]); + } + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/BinaryFunctionAction.java b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/BinaryFunctionAction.java new file mode 100644 index 00000000000..90d2f1dcd8e --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/BinaryFunctionAction.java @@ -0,0 +1,28 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2019 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.formulaeditor.function; + +public interface BinaryFunctionAction { + Double execute(Double firstArgument, Double secondArgument); +} diff --git a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/FormulaFunction.java b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/FormulaFunction.java new file mode 100644 index 00000000000..b5eba702a71 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/FormulaFunction.java @@ -0,0 +1,28 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2019 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.formulaeditor.function; + +public interface FormulaFunction { + Double execute(Double... args); +} diff --git a/catroid/src/main/java/org/catrobat/catroid/userbrick/UserBrickInput.java b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/FunctionProvider.java similarity index 75% rename from catroid/src/main/java/org/catrobat/catroid/userbrick/UserBrickInput.java rename to catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/FunctionProvider.java index 1589fe9dced..6b287475887 100644 --- a/catroid/src/main/java/org/catrobat/catroid/userbrick/UserBrickInput.java +++ b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/FunctionProvider.java @@ -1,6 +1,6 @@ /* * Catroid: An on-device visual programming system for Android devices - * Copyright (C) 2010-2020 The Catrobat Team + * Copyright (C) 2010-2019 The Catrobat Team * () * * This program is free software: you can redistribute it and/or modify @@ -21,19 +21,12 @@ * along with this program. If not, see . */ -package org.catrobat.catroid.userbrick; +package org.catrobat.catroid.formulaeditor.function; -import org.catrobat.catroid.common.Nameable; +import org.catrobat.catroid.formulaeditor.Functions; -public class UserBrickInput implements UserBrickData { +import java.util.Map; - Nameable input; - - public UserBrickInput(Nameable input) { - this.input = input; - } - - public Nameable getInput() { - return this.input; - } +public interface FunctionProvider { + void addFunctionsToMap(Map formulaFunctions); } diff --git a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/MathFunctionProvider.java b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/MathFunctionProvider.java new file mode 100644 index 00000000000..542fa54903e --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/MathFunctionProvider.java @@ -0,0 +1,85 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2019 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.formulaeditor.function; + +import org.catrobat.catroid.formulaeditor.Functions; + +import java.util.Map; + +import static org.catrobat.catroid.formulaeditor.common.Conversions.FALSE; +import static org.catrobat.catroid.formulaeditor.common.Conversions.TRUE; + +public class MathFunctionProvider implements FunctionProvider { + @Override + public void addFunctionsToMap(Map formulaFunctions) { + formulaFunctions.put(Functions.SIN, new UnaryFunction(argument -> Math.sin(Math.toRadians(argument)))); + formulaFunctions.put(Functions.COS, new UnaryFunction(argument -> Math.cos(Math.toRadians(argument)))); + formulaFunctions.put(Functions.TAN, new UnaryFunction(argument -> Math.tan(Math.toRadians(argument)))); + formulaFunctions.put(Functions.ARCTAN2, new BinaryFunction(this::interpretFunctionArcTan2)); + formulaFunctions.put(Functions.LN, new UnaryFunction(Math::log)); + formulaFunctions.put(Functions.LOG, new UnaryFunction(Math::log10)); + formulaFunctions.put(Functions.SQRT, new UnaryFunction(Math::sqrt)); + formulaFunctions.put(Functions.ABS, new UnaryFunction(Math::abs)); + formulaFunctions.put(Functions.ROUND, new UnaryFunction(argument -> (double) Math.round(argument))); + formulaFunctions.put(Functions.PI, args -> Math.PI); + formulaFunctions.put(Functions.ARCSIN, new UnaryFunction(argument -> Math.toDegrees(Math.asin(argument)))); + formulaFunctions.put(Functions.ARCCOS, new UnaryFunction(argument -> Math.toDegrees(Math.acos(argument)))); + formulaFunctions.put(Functions.ARCTAN, new UnaryFunction(argument -> Math.toDegrees(Math.atan(argument)))); + formulaFunctions.put(Functions.EXP, new UnaryFunction(Math::exp)); + formulaFunctions.put(Functions.POWER, new BinaryFunction(Math::pow)); + formulaFunctions.put(Functions.FLOOR, new UnaryFunction(Math::floor)); + formulaFunctions.put(Functions.CEIL, new UnaryFunction(Math::ceil)); + formulaFunctions.put(Functions.MAX, new BinaryFunction(Math::max)); + formulaFunctions.put(Functions.MIN, new BinaryFunction(Math::min)); + formulaFunctions.put(Functions.TRUE, args -> TRUE); + formulaFunctions.put(Functions.FALSE, args -> FALSE); + formulaFunctions.put(Functions.MOD, new BinaryFunction(this::interpretFunctionMod)); + } + + private double interpretFunctionMod(double dividend, double divisor) { + if (dividend == 0 || divisor == 0) { + return dividend; + } + + if (divisor > 0) { + while (dividend < 0) { + dividend += Math.abs(divisor); + } + } else { + if (dividend > 0) { + return (dividend % divisor) + divisor; + } + } + + return dividend % divisor; + } + + private double interpretFunctionArcTan2(double firstArgument, double secondArgument) { + if ((firstArgument == 0) && (secondArgument == 0)) { + return Math.random() * 360 - 180; + } else { + return Math.toDegrees(Math.atan2(firstArgument, secondArgument)); + } + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/RaspiFunctionProvider.java b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/RaspiFunctionProvider.java new file mode 100644 index 00000000000..509fd4c64e3 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/RaspiFunctionProvider.java @@ -0,0 +1,57 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2019 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.formulaeditor.function; + +import android.util.Log; + +import org.catrobat.catroid.devices.raspberrypi.RPiSocketConnection; +import org.catrobat.catroid.devices.raspberrypi.RaspberryPiService; +import org.catrobat.catroid.formulaeditor.Functions; + +import java.util.Map; + +public class RaspiFunctionProvider implements FunctionProvider { + @Override + public void addFunctionsToMap(Map formulaFunctions) { + formulaFunctions.put(Functions.RASPIDIGITAL, new UnaryFunction(this::interpretFunctionRaspiDigital)); + } + + private double interpretFunctionRaspiDigital(Double argument) { + RPiSocketConnection connection = RaspberryPiService.getInstance().connection; + if (argument == null) { + return 0d; + } + try { + int pin = argument.intValue(); + return booleanToDouble(connection.getPin(pin)); + } catch (Exception e) { + Log.e(getClass().getSimpleName(), "RPi: exception during getPin: " + e); + } + return 0d; + } + + private double booleanToDouble(boolean value) { + return value ? 1 : 0; + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/TouchFunctionProvider.java b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/TouchFunctionProvider.java new file mode 100644 index 00000000000..9a3a2c1f087 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/TouchFunctionProvider.java @@ -0,0 +1,54 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2019 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.formulaeditor.function; + +import org.catrobat.catroid.formulaeditor.Functions; +import org.catrobat.catroid.utils.TouchUtil; + +import java.util.Map; + +public class TouchFunctionProvider implements FunctionProvider { + @Override + public void addFunctionsToMap(Map formulaFunctions) { + formulaFunctions.put(Functions.MULTI_FINGER_TOUCHED, new UnaryFunction(this::interpretFunctionFingerTouched)); + formulaFunctions.put(Functions.MULTI_FINGER_X, new UnaryFunction(this::interpretFunctionMultiFingerX)); + formulaFunctions.put(Functions.MULTI_FINGER_Y, new UnaryFunction(this::interpretFunctionMultiFingerY)); + } + + private double interpretFunctionMultiFingerY(double argument) { + return TouchUtil.getY((int) argument); + } + + private double interpretFunctionMultiFingerX(double argument) { + return TouchUtil.getX((int) argument); + } + + private double interpretFunctionFingerTouched(double argument) { + return booleanToDouble(TouchUtil.isFingerTouching((int) argument)); + } + + private double booleanToDouble(boolean value) { + return value ? 1 : 0; + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/UnaryFunction.java b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/UnaryFunction.java new file mode 100644 index 00000000000..0a81b9f3024 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/UnaryFunction.java @@ -0,0 +1,50 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2019 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.formulaeditor.function; + +public class UnaryFunction implements FormulaFunction, UnaryFunctionAction { + private final UnaryFunctionAction action; + + public UnaryFunction(UnaryFunctionAction action) { + this.action = action; + } + + @Override + public Double execute(Double argument) { + if (argument == null) { + return 0d; + } else { + return action.execute(argument); + } + } + + @Override + public Double execute(Double... args) { + if (args == null || args.length < 1) { + return 0d; + } else { + return execute(args[0]); + } + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/UnaryFunctionAction.java b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/UnaryFunctionAction.java new file mode 100644 index 00000000000..20ca405ea94 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/formulaeditor/function/UnaryFunctionAction.java @@ -0,0 +1,28 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2019 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.formulaeditor.function; + +public interface UnaryFunctionAction { + Double execute(Double argument); +} diff --git a/catroid/src/main/java/org/catrobat/catroid/io/BackpackBrickSerializerAndDeserializer.java b/catroid/src/main/java/org/catrobat/catroid/io/BackpackInterfaceSerializerAndDeserializer.java similarity index 73% rename from catroid/src/main/java/org/catrobat/catroid/io/BackpackBrickSerializerAndDeserializer.java rename to catroid/src/main/java/org/catrobat/catroid/io/BackpackInterfaceSerializerAndDeserializer.java index 926a457e655..fb85cb03d04 100644 --- a/catroid/src/main/java/org/catrobat/catroid/io/BackpackBrickSerializerAndDeserializer.java +++ b/catroid/src/main/java/org/catrobat/catroid/io/BackpackInterfaceSerializerAndDeserializer.java @@ -33,30 +33,30 @@ import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; -import org.catrobat.catroid.content.bricks.Brick; import org.catrobat.catroid.ui.controller.BackpackListManager; import java.lang.reflect.Type; -public class BackpackBrickSerializerAndDeserializer implements JsonSerializer, JsonDeserializer { +public class BackpackInterfaceSerializerAndDeserializer implements JsonSerializer, + JsonDeserializer { - private static final String TAG = BackpackBrickSerializerAndDeserializer.class.getSimpleName(); + private static final String TAG = BackpackInterfaceSerializerAndDeserializer.class.getSimpleName(); - private static final String TYPE = "bricktype"; + private static final String TYPE = "type"; private static final String PROPERTY = "properties"; @Override - public JsonElement serialize(Brick brick, Type typeOfSrc, JsonSerializationContext context) { + public JsonElement serialize(T object, Type interfaceType, JsonSerializationContext context) { JsonObject jsonObject = new JsonObject(); - String packageName = brick.getClass().getPackage().getName(); - String className = brick.getClass().getSimpleName(); + String packageName = object.getClass().getPackage().getName(); + String className = object.getClass().getSimpleName(); jsonObject.add(TYPE, new JsonPrimitive(packageName + '.' + className)); - jsonObject.add(PROPERTY, context.serialize(brick, brick.getClass())); + jsonObject.add(PROPERTY, context.serialize(object, object.getClass())); return jsonObject; } @Override - public Brick deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { + public T deserialize(JsonElement json, Type interfaceType, JsonDeserializationContext context) { JsonObject jsonObject = json.getAsJsonObject(); String type = jsonObject.get(TYPE).getAsString(); JsonElement element = jsonObject.get(PROPERTY); @@ -65,7 +65,7 @@ public Brick deserialize(JsonElement json, Type typeOfT, JsonDeserializationCont try { classToDeserialize = Class.forName(type); } catch (ClassNotFoundException classNotFoundException) { - Log.e(TAG, "Could not deserialize backpacked brick element: " + type); + Log.e(TAG, "Could not deserialize backpacked element: " + type); BackpackListManager.getInstance().backpackFile.delete(); return null; } diff --git a/catroid/src/main/java/org/catrobat/catroid/io/BackpackSerializer.java b/catroid/src/main/java/org/catrobat/catroid/io/BackpackSerializer.java index f5e3d0a1e42..ffedbe8316b 100644 --- a/catroid/src/main/java/org/catrobat/catroid/io/BackpackSerializer.java +++ b/catroid/src/main/java/org/catrobat/catroid/io/BackpackSerializer.java @@ -33,6 +33,7 @@ import org.catrobat.catroid.common.Backpack; import org.catrobat.catroid.content.Script; import org.catrobat.catroid.content.bricks.Brick; +import org.catrobat.catroid.userbrick.UserDefinedBrickData; import java.io.BufferedReader; import java.io.File; @@ -52,7 +53,8 @@ public BackpackSerializer(File backpackFile) { this.backpackFile = backpackFile; GsonBuilder gsonBuilder = new GsonBuilder().enableComplexMapKeySerialization().setPrettyPrinting(); gsonBuilder.registerTypeAdapter(Script.class, new BackpackScriptSerializerAndDeserializer()); - gsonBuilder.registerTypeAdapter(Brick.class, new BackpackBrickSerializerAndDeserializer()); + gsonBuilder.registerTypeAdapter(Brick.class, new BackpackInterfaceSerializerAndDeserializer()); + gsonBuilder.registerTypeAdapter(UserDefinedBrickData.class, new BackpackInterfaceSerializerAndDeserializer()); backpackGson = gsonBuilder.create(); } diff --git a/catroid/src/main/java/org/catrobat/catroid/io/BackwardCompatibleCatrobatLanguageXStream.java b/catroid/src/main/java/org/catrobat/catroid/io/BackwardCompatibleCatrobatLanguageXStream.java index 4f7740b6dfb..c636f84fc2c 100644 --- a/catroid/src/main/java/org/catrobat/catroid/io/BackwardCompatibleCatrobatLanguageXStream.java +++ b/catroid/src/main/java/org/catrobat/catroid/io/BackwardCompatibleCatrobatLanguageXStream.java @@ -31,6 +31,7 @@ import org.catrobat.catroid.content.BroadcastScript; import org.catrobat.catroid.content.RaspiInterruptScript; import org.catrobat.catroid.content.StartScript; +import org.catrobat.catroid.content.UserDefinedScript; import org.catrobat.catroid.content.WhenBackgroundChangesScript; import org.catrobat.catroid.content.WhenBounceOffScript; import org.catrobat.catroid.content.WhenConditionScript; @@ -153,6 +154,7 @@ import org.catrobat.catroid.content.bricks.TurnLeftSpeedBrick; import org.catrobat.catroid.content.bricks.TurnRightBrick; import org.catrobat.catroid.content.bricks.TurnRightSpeedBrick; +import org.catrobat.catroid.content.bricks.UserDefinedBrick; import org.catrobat.catroid.content.bricks.VibrationBrick; import org.catrobat.catroid.content.bricks.WaitBrick; import org.catrobat.catroid.content.bricks.WhenBounceOffBrick; @@ -381,6 +383,9 @@ private void initializeBrickInfoMap() { brickInfo.addBrickFieldToMap("digitalPinNumber", BrickField.IF_CONDITION); brickInfoMap.put("raspiIfLogicBeginBrick", brickInfo); + brickInfo = new BrickInfo(UserDefinedBrick.class.getSimpleName()); + brickInfoMap.put("userDefinedBrick", brickInfo); + brickInfo = new BrickInfo(LoopEndBrick.class.getSimpleName()); brickInfoMap.put("loopEndBrick", brickInfo); @@ -704,6 +709,7 @@ private void initializeScriptInfoMap() { scriptInfoMap.put("collisionScript", WhenBounceOffScript.class.getSimpleName()); scriptInfoMap.put("whenTouchDownScript", WhenTouchDownScript.class.getSimpleName()); scriptInfoMap.put("whenGamepadButtonScript", WhenGamepadButtonScript.class.getSimpleName()); + scriptInfoMap.put("userDefinedScript", UserDefinedScript.class.getSimpleName()); } private void modifyXMLToSupportUnknownFields(File file) { diff --git a/catroid/src/main/java/org/catrobat/catroid/io/ProjectAndSceneScreenshotLoader.java b/catroid/src/main/java/org/catrobat/catroid/io/ProjectAndSceneScreenshotLoader.java index b590d835c42..a3e324d94b9 100644 --- a/catroid/src/main/java/org/catrobat/catroid/io/ProjectAndSceneScreenshotLoader.java +++ b/catroid/src/main/java/org/catrobat/catroid/io/ProjectAndSceneScreenshotLoader.java @@ -136,7 +136,7 @@ public void run() { projectAndSceneImage = null; } else { projectAndSceneImage = ImageEditing.getScaledBitmapFromPath(pathOfScreenshot, thumbnailWidth, thumbnailHeight, - ImageEditing.ResizeType.STAY_IN_RECTANGLE_WITH_SAME_ASPECT_RATIO, true); + ImageEditing.ResizeType.FILL_RECTANGLE_WITH_SAME_ASPECT_RATIO, true); } String screenshotName = ""; diff --git a/catroid/src/main/java/org/catrobat/catroid/io/SoundManager.java b/catroid/src/main/java/org/catrobat/catroid/io/SoundManager.java index 93e0d56a097..aa683cb1f3f 100644 --- a/catroid/src/main/java/org/catrobat/catroid/io/SoundManager.java +++ b/catroid/src/main/java/org/catrobat/catroid/io/SoundManager.java @@ -25,10 +25,15 @@ import android.media.MediaPlayer; import android.util.Log; +import org.catrobat.catroid.content.MediaPlayerWithSoundDetails; +import org.catrobat.catroid.content.SoundBackup; +import org.catrobat.catroid.content.SoundFilePathWithSprite; +import org.catrobat.catroid.content.Sprite; + import java.util.ArrayList; -import java.util.HashMap; +import java.util.HashSet; import java.util.List; -import java.util.Map; +import java.util.Set; import androidx.annotation.VisibleForTesting; @@ -42,9 +47,10 @@ public class SoundManager { private static final String TAG = SoundManager.class.getSimpleName(); private static final SoundManager INSTANCE = new SoundManager(); - private final List mediaPlayers = new ArrayList(MAX_MEDIA_PLAYERS); + private final List mediaPlayers = new ArrayList<>(MAX_MEDIA_PLAYERS); private float volume = 70.0f; - private List soundFiles = new ArrayList<>(MAX_MEDIA_PLAYERS); + + private final Set recentlyStoppedSoundfilePaths = new HashSet<>(); @VisibleForTesting public SoundManager() { @@ -54,26 +60,43 @@ public static SoundManager getInstance() { return INSTANCE; } - public synchronized void playSoundFile(String pathToSoundfile) { - playSoundFileWithStartTime(pathToSoundfile, 0); + public synchronized void playSoundFile(String soundFilePath, Sprite sprite) { + playSoundFileWithStartTime(soundFilePath, sprite, 0); } - public synchronized void playSoundFileWithStartTime(String pathToSoundfile, - int startTimeInMilSeconds) { - MediaPlayer mediaPlayer = getAvailableMediaPlayer(); + public synchronized void playSoundFileWithStartTime(String soundFilePath, + Sprite sprite, int startTimeInMilSeconds) { + stopSameSoundInSprite(soundFilePath, sprite); + MediaPlayerWithSoundDetails mediaPlayer = getAvailableMediaPlayer(); if (mediaPlayer != null) { try { - soundFiles.add(pathToSoundfile); - mediaPlayer.setDataSource(pathToSoundfile); + mediaPlayer.setStartedBySprite(sprite); + mediaPlayer.setPathToSoundFile(soundFilePath); + mediaPlayer.setDataSource(soundFilePath); mediaPlayer.prepare(); mediaPlayer.seekTo(startTimeInMilSeconds); mediaPlayer.start(); } catch (Exception exception) { - Log.e(TAG, "Couldn't play sound file '" + pathToSoundfile + "'", exception); + Log.e(TAG, "Couldn't play sound file '" + soundFilePath + "'", exception); + } + } + } + + public synchronized void stopSameSoundInSprite(String pathToSoundFile, Sprite sprite) { + for (MediaPlayerWithSoundDetails mediaPlayer : mediaPlayers) { + if (mediaPlayer.isPlaying() && mediaPlayer.getStartedBySprite() == sprite + && mediaPlayer.getPathToSoundFile().equals(pathToSoundFile)) { + mediaPlayer.stop(); + recentlyStoppedSoundfilePaths.add(new SoundFilePathWithSprite( + mediaPlayer.getPathToSoundFile(), sprite)); } } } + public synchronized Set getRecentlyStoppedSoundfilePaths() { + return recentlyStoppedSoundfilePaths; + } + public synchronized float getDurationOfSoundFile(String pathToSoundfile) { MediaPlayer mediaPlayer = getAvailableMediaPlayer(); float duration = 0f; @@ -90,8 +113,8 @@ public synchronized float getDurationOfSoundFile(String pathToSoundfile) { return duration; } - private MediaPlayer getAvailableMediaPlayer() { - for (MediaPlayer mediaPlayer : mediaPlayers) { + private MediaPlayerWithSoundDetails getAvailableMediaPlayer() { + for (MediaPlayerWithSoundDetails mediaPlayer : mediaPlayers) { if (!mediaPlayer.isPlaying()) { mediaPlayer.reset(); return mediaPlayer; @@ -99,7 +122,7 @@ private MediaPlayer getAvailableMediaPlayer() { } if (mediaPlayers.size() < MAX_MEDIA_PLAYERS) { - MediaPlayer mediaPlayer = new MediaPlayer(); + MediaPlayerWithSoundDetails mediaPlayer = new MediaPlayerWithSoundDetails(); mediaPlayers.add(mediaPlayer); setVolume(volume); return mediaPlayer; @@ -127,11 +150,12 @@ public synchronized float getVolume() { } public synchronized void clear() { - for (MediaPlayer mediaPlayer : mediaPlayers) { + for (MediaPlayerWithSoundDetails mediaPlayer : mediaPlayers) { + mediaPlayer.reset(); mediaPlayer.release(); } mediaPlayers.clear(); - soundFiles.clear(); + recentlyStoppedSoundfilePaths.clear(); } public synchronized void pause() { @@ -160,29 +184,17 @@ public synchronized void stopAllSounds() { } } - public Map getPlayingSoundDurationMap() { - List positionList = - SoundManager.getInstance().getCurrentPositionOfSounds(); - Map soundsDurationMap = new HashMap<>(); - for (String sound : soundFiles) { - int position = positionList.get(soundFiles.indexOf(sound)); - if (position > 0 && position < getDurationOfSoundFile(sound)) { - soundsDurationMap.put(sound, position); + public List getPlayingSoundBackups() { + List backupList = new ArrayList<>(); + for (MediaPlayerWithSoundDetails mediaPlayer : mediaPlayers) { + if (mediaPlayer.isPlaying()) { + backupList.add(new SoundBackup(mediaPlayer.getPathToSoundFile(), mediaPlayer.getStartedBySprite(), mediaPlayer.getCurrentPosition())); } } - return soundsDurationMap; + return backupList; } - private List getCurrentPositionOfSounds() { - List positionList = new ArrayList<>(); - for (MediaPlayer mediaPlayer : mediaPlayers) { - positionList.add(mediaPlayer.getCurrentPosition()); - } - return positionList; - } - - @VisibleForTesting - public List getMediaPlayers() { + public List getMediaPlayers() { return mediaPlayers; } } diff --git a/catroid/src/main/java/org/catrobat/catroid/io/XStreamUserDataHashMapConverter.java b/catroid/src/main/java/org/catrobat/catroid/io/XStreamUserDataHashMapConverter.java new file mode 100644 index 00000000000..d04da52d902 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/io/XStreamUserDataHashMapConverter.java @@ -0,0 +1,82 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2018 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.io; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +import org.catrobat.catroid.content.bricks.Brick; +import org.catrobat.catroid.content.bricks.UserDataHashMap; +import org.catrobat.catroid.formulaeditor.UserData; +import org.catrobat.catroid.formulaeditor.UserList; +import org.catrobat.catroid.formulaeditor.UserVariable; + +public class XStreamUserDataHashMapConverter implements Converter { + + private static final String USERDATA = "userData"; + private static final String CATEGORY = "category"; + + @Override + public boolean canConvert(Class type) { + return type.equals(UserDataHashMap.class); + } + + @Override + public void marshal(Object object, HierarchicalStreamWriter hierarchicalStreamWriter, + MarshallingContext marshallingContext) { + UserDataHashMap userDataHashMap = (UserDataHashMap) object; + for (Brick.BrickData brickData : userDataHashMap.keySet()) { + hierarchicalStreamWriter.startNode(USERDATA); + hierarchicalStreamWriter.addAttribute(CATEGORY, brickData.toString()); + if (userDataHashMap.get(brickData) != null) { + marshallingContext.convertAnother(userDataHashMap.get(brickData)); + } + hierarchicalStreamWriter.endNode(); + } + } + + @Override + public Object unmarshal(HierarchicalStreamReader hierarchicalStreamReader, UnmarshallingContext unmarshallingContext) { + UserDataHashMap userDataHashMap = new UserDataHashMap(); + while (hierarchicalStreamReader.hasMoreChildren()) { + hierarchicalStreamReader.moveDown(); + Brick.BrickData brickData = + Brick.BrickData.valueOf(hierarchicalStreamReader.getAttribute(CATEGORY)); + UserData userData; + if (Brick.BrickData.isUserList(brickData)) { + userData = (UserData) unmarshallingContext.convertAnother(userDataHashMap, + UserList.class); + } else { + userData = (UserData) unmarshallingContext.convertAnother(userDataHashMap, + UserVariable.class); + } + hierarchicalStreamReader.moveUp(); + + userDataHashMap.put(brickData, userData); + } + return userDataHashMap; + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/io/XstreamSerializer.java b/catroid/src/main/java/org/catrobat/catroid/io/XstreamSerializer.java index 0285c0c466d..11fc11eb3ef 100644 --- a/catroid/src/main/java/org/catrobat/catroid/io/XstreamSerializer.java +++ b/catroid/src/main/java/org/catrobat/catroid/io/XstreamSerializer.java @@ -49,6 +49,7 @@ import org.catrobat.catroid.content.SingleSprite; import org.catrobat.catroid.content.Sprite; import org.catrobat.catroid.content.StartScript; +import org.catrobat.catroid.content.UserDefinedScript; import org.catrobat.catroid.content.WhenBackgroundChangesScript; import org.catrobat.catroid.content.WhenBounceOffScript; import org.catrobat.catroid.content.WhenClonedScript; @@ -69,6 +70,7 @@ import org.catrobat.catroid.content.bricks.AskBrick; import org.catrobat.catroid.content.bricks.AskSpeechBrick; import org.catrobat.catroid.content.bricks.AssertEqualsBrick; +import org.catrobat.catroid.content.bricks.AssertUserListsBrick; import org.catrobat.catroid.content.bricks.BackgroundRequestBrick; import org.catrobat.catroid.content.bricks.BroadcastBrick; import org.catrobat.catroid.content.bricks.BroadcastReceiverBrick; @@ -105,6 +107,7 @@ import org.catrobat.catroid.content.bricks.DroneTurnRightBrick; import org.catrobat.catroid.content.bricks.FinishStageBrick; import org.catrobat.catroid.content.bricks.FlashBrick; +import org.catrobat.catroid.content.bricks.ForVariableFromToBrick; import org.catrobat.catroid.content.bricks.ForeverBrick; import org.catrobat.catroid.content.bricks.GlideToBrick; import org.catrobat.catroid.content.bricks.GoNStepsBackBrick; @@ -163,6 +166,7 @@ import org.catrobat.catroid.content.bricks.RaspiSendDigitalValueBrick; import org.catrobat.catroid.content.bricks.ReadListFromDeviceBrick; import org.catrobat.catroid.content.bricks.ReadVariableFromDeviceBrick; +import org.catrobat.catroid.content.bricks.ReadVariableFromFileBrick; import org.catrobat.catroid.content.bricks.RepeatBrick; import org.catrobat.catroid.content.bricks.RepeatUntilBrick; import org.catrobat.catroid.content.bricks.ReplaceItemInUserListBrick; @@ -206,6 +210,7 @@ import org.catrobat.catroid.content.bricks.StopAllSoundsBrick; import org.catrobat.catroid.content.bricks.StopRunningStitchBrick; import org.catrobat.catroid.content.bricks.StopScriptBrick; +import org.catrobat.catroid.content.bricks.StopSoundBrick; import org.catrobat.catroid.content.bricks.StoreCSVIntoUserListBrick; import org.catrobat.catroid.content.bricks.TapAtBrick; import org.catrobat.catroid.content.bricks.ThinkBubbleBrick; @@ -216,6 +221,7 @@ import org.catrobat.catroid.content.bricks.TurnRightBrick; import org.catrobat.catroid.content.bricks.TurnRightSpeedBrick; import org.catrobat.catroid.content.bricks.UserDefinedBrick; +import org.catrobat.catroid.content.bricks.UserDefinedReceiverBrick; import org.catrobat.catroid.content.bricks.UserListBrick; import org.catrobat.catroid.content.bricks.UserVariableBrickWithFormula; import org.catrobat.catroid.content.bricks.VibrationBrick; @@ -235,10 +241,14 @@ import org.catrobat.catroid.content.bricks.WhenTouchDownBrick; import org.catrobat.catroid.content.bricks.WriteListOnDeviceBrick; import org.catrobat.catroid.content.bricks.WriteVariableOnDeviceBrick; +import org.catrobat.catroid.content.bricks.WriteVariableToFileBrick; import org.catrobat.catroid.content.bricks.ZigZagStitchBrick; import org.catrobat.catroid.exceptions.LoadingProjectException; import org.catrobat.catroid.formulaeditor.UserList; import org.catrobat.catroid.formulaeditor.UserVariable; +import org.catrobat.catroid.userbrick.UserDefinedBrickData; +import org.catrobat.catroid.userbrick.UserDefinedBrickInput; +import org.catrobat.catroid.userbrick.UserDefinedBrickLabel; import org.catrobat.catroid.utils.StringFinder; import java.io.File; @@ -292,10 +302,15 @@ private void prepareXstream(Class projectClass, Class sceneClass) { xstream.processAnnotations(Setting.class); xstream.processAnnotations(UserVariableBrickWithFormula.class); xstream.processAnnotations(UserListBrick.class); + xstream.processAnnotations(UserDefinedBrickData.class); + xstream.processAnnotations(UserDefinedBrickInput.class); + xstream.processAnnotations(UserDefinedBrickLabel.class); xstream.registerConverter(new XStreamConcurrentFormulaHashMapConverter()); + xstream.registerConverter(new XStreamUserDataHashMapConverter()); xstream.registerConverter(new XStreamUserVariableConverter(xstream.getMapper(), xstream.getReflectionProvider(), xstream.getClassLoaderReference())); + xstream.registerConverter(new XStreamBrickConverter(xstream.getMapper(), xstream.getReflectionProvider())); xstream.registerConverter(new XStreamScriptConverter(xstream.getMapper(), xstream.getReflectionProvider())); xstream.registerConverter(new XStreamSpriteConverter(xstream.getMapper(), xstream.getReflectionProvider())); @@ -322,6 +337,7 @@ private void prepareXstream(Class projectClass, Class sceneClass) { xstream.omitField(ShowTextBrick.class, "userVariableName"); xstream.omitField(HideTextBrick.class, "userVariableName"); xstream.omitField(HideTextBrick.class, "formulaList"); + xstream.omitField(HideTextBrick.class, "userDataList"); xstream.omitField(SayBubbleBrick.class, "type"); xstream.omitField(SayBubbleBrick.class, "type"); @@ -356,6 +372,7 @@ private void prepareXstream(Class projectClass, Class sceneClass) { xstream.alias("script", RaspiInterruptScript.class); xstream.alias("script", WhenTouchDownScript.class); xstream.alias("script", WhenBackgroundChangesScript.class); + xstream.alias("script", UserDefinedScript.class); xstream.alias("brick", AddItemToUserListBrick.class); xstream.alias("brick", AskBrick.class); @@ -391,6 +408,7 @@ private void prepareXstream(Class projectClass, Class sceneClass) { xstream.alias("brick", IfThenLogicEndBrick.class); xstream.alias("brick", UserDefinedBrick.class); + xstream.alias("brick", UserDefinedReceiverBrick.class); xstream.alias("brick", IfOnEdgeBounceBrick.class); xstream.alias("brick", InsertItemIntoUserListBrick.class); xstream.alias("brick", FlashBrick.class); @@ -418,6 +436,7 @@ private void prepareXstream(Class projectClass, Class sceneClass) { xstream.alias("brick", PreviousLookBrick.class); xstream.alias("brick", RepeatBrick.class); xstream.alias("brick", RepeatUntilBrick.class); + xstream.alias("brick", ForVariableFromToBrick.class); xstream.alias("brick", ReplaceItemInUserListBrick.class); xstream.alias("brick", SceneTransitionBrick.class); xstream.alias("brick", SceneStartBrick.class); @@ -443,6 +462,7 @@ private void prepareXstream(Class projectClass, Class sceneClass) { xstream.alias("brick", SpeakBrick.class); xstream.alias("brick", SpeakAndWaitBrick.class); xstream.alias("brick", StampBrick.class); + xstream.alias("brick", StopSoundBrick.class); xstream.alias("brick", StopAllSoundsBrick.class); xstream.alias("brick", ThinkBubbleBrick.class); xstream.alias("brick", SayBubbleBrick.class); @@ -459,8 +479,10 @@ private void prepareXstream(Class projectClass, Class sceneClass) { xstream.alias("brick", WhenStartedBrick.class); xstream.alias("brick", WhenClonedBrick.class); xstream.alias("brick", WriteVariableOnDeviceBrick.class); + xstream.alias("brick", ReadVariableFromFileBrick.class); xstream.alias("brick", WriteListOnDeviceBrick.class); xstream.alias("brick", ReadVariableFromDeviceBrick.class); + xstream.alias("brick", WriteVariableToFileBrick.class); xstream.alias("brick", ReadListFromDeviceBrick.class); xstream.alias("brick", StopScriptBrick.class); xstream.alias("brick", WebRequestBrick.class); @@ -506,6 +528,7 @@ private void prepareXstream(Class projectClass, Class sceneClass) { xstream.alias("brick", AssertEqualsBrick.class); xstream.alias("brick", FinishStageBrick.class); + xstream.alias("brick", AssertUserListsBrick.class); xstream.alias("brick", TapAtBrick.class); xstream.alias("brick", DroneFlipBrick.class); diff --git a/catroid/src/main/java/org/catrobat/catroid/sensing/CollisionDetection.java b/catroid/src/main/java/org/catrobat/catroid/sensing/CollisionDetection.java index 2e1bbf5e47a..a6a133aba42 100644 --- a/catroid/src/main/java/org/catrobat/catroid/sensing/CollisionDetection.java +++ b/catroid/src/main/java/org/catrobat/catroid/sensing/CollisionDetection.java @@ -30,12 +30,11 @@ import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; -import org.catrobat.catroid.ProjectManager; import org.catrobat.catroid.common.Constants; import org.catrobat.catroid.content.Look; +import org.catrobat.catroid.content.Project; import org.catrobat.catroid.content.Scene; import org.catrobat.catroid.content.Sprite; -import org.catrobat.catroid.utils.TouchUtil; import java.util.ArrayList; @@ -44,19 +43,17 @@ public final class CollisionDetection { private CollisionDetection() { } - public static double checkCollisionBetweenLooks(Look firstLook, Look secondLook) { + public static boolean checkCollisionBetweenLooks(Look firstLook, Look secondLook) { if (!firstLook.isVisible() || !firstLook.isLookVisible() || !secondLook.isVisible() || !secondLook.isLookVisible()) { - return 0d; + return false; } if (!firstLook.getHitbox().overlaps(secondLook.getHitbox())) { - return 0d; + return false; } - boolean colliding = checkCollisionBetweenPolygons(firstLook.getCurrentCollisionPolygon(), + return checkCollisionBetweenPolygons(firstLook.getCurrentCollisionPolygon(), secondLook.getCurrentCollisionPolygon()); - - return colliding ? 1d : 0d; } public static boolean checkCollisionBetweenPolygons(Polygon[] first, Polygon[] second) { @@ -128,10 +125,10 @@ public static boolean checkCollisionForPolygonsInPolygons(Polygon[] first, Polyg return false; } - public static String getSecondSpriteNameFromCollisionFormulaString(String formula) { + public static String getSecondSpriteNameFromCollisionFormulaString(String formula, Project currentProject) { int indexOfSpriteInFormula = formula.length(); - for (Scene scene : ProjectManager.getInstance().getCurrentProject().getSceneList()) { + for (Scene scene : currentProject.getSceneList()) { for (Sprite sprite : scene.getSpriteList()) { int index = formula.lastIndexOf(sprite.getName()); if (index > 0 && index + sprite.getName().length() == formula.length() && index @@ -143,33 +140,30 @@ public static String getSecondSpriteNameFromCollisionFormulaString(String formul if (indexOfSpriteInFormula >= formula.length()) { return null; } - String secondSpriteName = formula.substring(indexOfSpriteInFormula, formula.length()); + String secondSpriteName = formula.substring(indexOfSpriteInFormula); return secondSpriteName; } - public static double collidesWithEdge(Look look) { - int virtualScreenWidth = ProjectManager.getInstance().getCurrentProject().getXmlHeader().virtualScreenWidth; - int virtualScreenHeight = ProjectManager.getInstance().getCurrentProject().getXmlHeader().virtualScreenHeight; - Rectangle screen = new Rectangle(-virtualScreenWidth / 2, -virtualScreenHeight / 2, virtualScreenWidth, - virtualScreenHeight); + public static boolean collidesWithEdge(Polygon[] currentCollisionPolygon, Rectangle screen) { + Vector2 firstPoint = new Vector2(); + Vector2 secondPoint = new Vector2(); //check if any line of the collision polygons intersects with the screen boundary - for (Polygon polygon : look.getCurrentCollisionPolygon()) { - for (int i = 0; i < polygon.getTransformedVertices().length - 4; i += 2) { - Vector2 firstPoint = new Vector2(polygon.getTransformedVertices()[i], - polygon.getTransformedVertices()[i + 1]); - Vector2 secondPoint = new Vector2(polygon.getTransformedVertices()[i + 2], - polygon.getTransformedVertices()[i + 3]); + for (Polygon polygon : currentCollisionPolygon) { + float[] transformedVertices = polygon.getTransformedVertices(); + for (int i = 0; i < transformedVertices.length - 4; i += 2) { + firstPoint.set(transformedVertices[i], transformedVertices[i + 1]); + secondPoint.set(transformedVertices[i + 2], transformedVertices[i + 3]); //if the line crosses the screen boarder, a collision is detected if (screen.contains(firstPoint) ^ screen.contains(secondPoint)) { - return 1.0d; + return true; } } } - return 0d; + return false; } - public static double collidesWithFinger(Look look) { + public static double collidesWithFinger(Polygon[] currentCollisionPolygon, ArrayList touchingPoints) { /*The touching points are expanded to circles with Constants.COLLISION_WITH_FINGER_TOUCH_RADIUS to simulate the real touching area of the finger (which is not only a point, but a small area) To improve performance first check if the circle is contained in the bounding box of that polygon @@ -183,7 +177,6 @@ to simulate the real touching area of the finger (which is not only a point, but | |___| | |_______| */ - ArrayList touchingPoints = TouchUtil.getCurrentTouchingPoints(); Vector2 start = new Vector2(); Vector2 end = new Vector2(); Vector2 center = new Vector2(); @@ -192,7 +185,7 @@ to simulate the real touching area of the finger (which is not only a point, but for (PointF point : touchingPoints) { center.set(point.x, point.y); int containedIn = 0; - for (Polygon polygon : look.getCurrentCollisionPolygon()) { + for (Polygon polygon : currentCollisionPolygon) { Rectangle boundingRectangle = polygon.getBoundingRectangle(); boundingRectangle.x -= touchRadius; boundingRectangle.y -= touchRadius; diff --git a/catroid/src/main/java/org/catrobat/catroid/stage/BrickDialogManager.kt b/catroid/src/main/java/org/catrobat/catroid/stage/BrickDialogManager.kt new file mode 100644 index 00000000000..6ebe70033d2 --- /dev/null +++ b/catroid/src/main/java/org/catrobat/catroid/stage/BrickDialogManager.kt @@ -0,0 +1,172 @@ +/* + * Catroid: An on-device visual programming system for Android devices + * Copyright (C) 2010-2020 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.stage + +import android.app.AlertDialog +import android.app.Dialog +import android.content.DialogInterface +import android.text.method.LinkMovementMethod +import android.view.ContextThemeWrapper +import android.view.KeyEvent +import android.view.LayoutInflater +import android.view.WindowManager +import android.widget.EditText +import android.widget.TextView +import androidx.core.text.HtmlCompat +import com.badlogic.gdx.scenes.scene2d.Action +import org.catrobat.catroid.BuildConfig +import org.catrobat.catroid.R +import org.catrobat.catroid.TrustedDomainManager +import org.catrobat.catroid.common.Constants +import org.catrobat.catroid.content.actions.AskAction +import org.catrobat.catroid.content.actions.WebAction +import java.net.URI +import java.util.ArrayList +import java.util.Collections + +class BrickDialogManager(val stageActivity: StageActivity) : + DialogInterface.OnKeyListener, DialogInterface.OnDismissListener { + + private val openDialogs = Collections.synchronizedList(ArrayList()) + + enum class DialogType { + ASK_DIALOG, + WEB_ACCESS_DIALOG + } + + fun dialogIsShowing() = openDialogs.isNotEmpty() + + fun dismissAllDialogs() { + openDialogs.forEach { it.dismiss() } + openDialogs.clear() + } + + fun showDialog(type: DialogType, action: Action, content: String) { + val dialog = when (type) { + DialogType.ASK_DIALOG -> createAskDialog(action as AskAction, content) + DialogType.WEB_ACCESS_DIALOG -> createWebAccessDialog(action as WebAction, content) + } + openDialog(dialog) + } + + private fun openDialog(dialog: Dialog) { + StageLifeCycleController.stagePause(stageActivity) + openDialogs.add(dialog) + dialog.show() + } + + private fun createAskDialog(askAction: AskAction, question: String): Dialog { + val editText = EditText(stageActivity) + val askDialog = AlertDialog.Builder(ContextThemeWrapper(stageActivity, R.style.Theme_AppCompat_Dialog)) + .setView(editText) + .setMessage(stageActivity.getString(R.string.brick_ask_dialog_hint)) + .setTitle(question) + .setCancelable(false) + .setOnKeyListener(this) + .setOnDismissListener(this) + .setPositiveButton(stageActivity.getString(R.string.brick_ask_dialog_submit)) { _, _ -> + askAction.setAnswerText(editText.text.toString()) + } + .create() + + editText.requestFocus() + askDialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE) + return askDialog + } + + private fun createWebAccessDialog(webAction: WebAction, url: String): Dialog { + val view = LayoutInflater.from(stageActivity).inflate(R.layout.dialog_web_access, null) + view.findViewById(R.id.request_url).text = url + + view.findViewById(R.id.request_warning).apply { + text = HtmlCompat.fromHtml( + stageActivity.getString(R.string.web_request_warning_message, Constants.WEB_REQUEST_WIKI_URL), + HtmlCompat.FROM_HTML_MODE_LEGACY + ) + movementMethod = LinkMovementMethod.getInstance() + } + + return AlertDialog.Builder(ContextThemeWrapper(stageActivity, R.style.Theme_AppCompat_Dialog)) + .setTitle(stageActivity.getString(R.string.web_request_warning_title)) + .setCancelable(false) + .setView(view) + .setOnKeyListener(this) + .setOnDismissListener(this) + .setPositiveButton(stageActivity.getString(R.string.once)) { _, _ -> + webAction.grantPermission() + } + .setNeutralButton(stageActivity.getString(R.string.always)) { dialog, _ -> + openDialog(createTrustDomainDialog(webAction, url, dialog as Dialog)) + } + .setNegativeButton(stageActivity.getString(R.string.deny)) { _, _ -> + webAction.denyPermission() + } + .create() + } + + private fun createTrustDomainDialog(webAction: WebAction, url: String, webAccessDialog: Dialog): Dialog { + val domain = URI(url).host.removePrefix("www.") + val view = LayoutInflater.from(stageActivity).inflate(R.layout.dialog_web_access, null) + view.findViewById(R.id.request_url).text = domain + + val warningMessage = StringBuilder() + .append(stageActivity.getString(R.string.web_request_warning_message, Constants.WEB_REQUEST_WIKI_URL)) + .append("

") + .append(stageActivity.getString(R.string.web_request_trust_domain_warning_message)) + + if (!BuildConfig.FEATURE_APK_GENERATOR_ENABLED) { + warningMessage.append(" ").append(stageActivity.getString(R.string.trusted_domains_edit_hint)) + } + + view.findViewById(R.id.request_warning).apply { + text = HtmlCompat.fromHtml(warningMessage.toString(), HtmlCompat.FROM_HTML_MODE_LEGACY) + movementMethod = LinkMovementMethod.getInstance() + } + + return AlertDialog.Builder(ContextThemeWrapper(stageActivity, R.style.Theme_AppCompat_Dialog)) + .setTitle(stageActivity.getString(R.string.web_request_trust_domain_warning_title)) + .setCancelable(false) + .setView(view) + .setOnKeyListener(this) + .setOnDismissListener(this) + .setPositiveButton(stageActivity.getString(R.string.always)) { _, _ -> + TrustedDomainManager.addToUserTrustList(domain) + webAction.grantPermission() + } + .setNeutralButton(stageActivity.getString(R.string.cancel)) { _, _ -> + openDialog(webAccessDialog) + } + .create() + } + + override fun onKey(dialog: DialogInterface, keyCode: Int, event: KeyEvent) = + (keyCode == KeyEvent.KEYCODE_BACK).also { + if (it) stageActivity.onBackPressed() + } + + override fun onDismiss(dialog: DialogInterface) { + openDialogs.remove(dialog as Dialog) + StageLifeCycleController.stageResume(stageActivity) + } +} diff --git a/catroid/src/main/java/org/catrobat/catroid/stage/StageActivity.java b/catroid/src/main/java/org/catrobat/catroid/stage/StageActivity.java index 1e72194e415..3229eb80ea0 100644 --- a/catroid/src/main/java/org/catrobat/catroid/stage/StageActivity.java +++ b/catroid/src/main/java/org/catrobat/catroid/stage/StageActivity.java @@ -23,7 +23,6 @@ package org.catrobat.catroid.stage; import android.app.Activity; -import android.app.AlertDialog; import android.app.PendingIntent; import android.content.Intent; import android.nfc.NdefMessage; @@ -34,34 +33,26 @@ import android.os.Looper; import android.os.Message; import android.speech.RecognizerIntent; -import android.text.Html; -import android.text.method.LinkMovementMethod; import android.util.Log; import android.util.SparseArray; -import android.view.ContextThemeWrapper; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowManager; -import android.widget.EditText; -import android.widget.TextView; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.backends.android.AndroidApplication; import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration; import com.badlogic.gdx.backends.android.AndroidGraphics; +import com.badlogic.gdx.scenes.scene2d.Action; import org.catrobat.catroid.BuildConfig; import org.catrobat.catroid.ProjectManager; import org.catrobat.catroid.R; import org.catrobat.catroid.bluetooth.base.BluetoothDeviceService; import org.catrobat.catroid.common.CatroidService; -import org.catrobat.catroid.common.Constants; import org.catrobat.catroid.common.ScreenValues; import org.catrobat.catroid.common.ServiceProvider; +import org.catrobat.catroid.content.Project; import org.catrobat.catroid.content.Scene; -import org.catrobat.catroid.content.actions.AskAction; -import org.catrobat.catroid.content.actions.WebAction; +import org.catrobat.catroid.content.Sprite; +import org.catrobat.catroid.content.bricks.Brick; import org.catrobat.catroid.devices.raspberrypi.RaspberryPiService; import org.catrobat.catroid.drone.jumpingsumo.JumpingSumoDeviceController; import org.catrobat.catroid.drone.jumpingsumo.JumpingSumoInitializer; @@ -71,6 +62,8 @@ import org.catrobat.catroid.ui.MarketingActivity; import org.catrobat.catroid.ui.dialogs.StageDialog; import org.catrobat.catroid.ui.recyclerview.dialog.PlaySceneDialog; +import org.catrobat.catroid.ui.runtimepermissions.BrickResourcesToRuntimePermissions; +import org.catrobat.catroid.ui.runtimepermissions.PermissionAdaptingActivity; import org.catrobat.catroid.ui.runtimepermissions.PermissionHandlingActivity; import org.catrobat.catroid.ui.runtimepermissions.PermissionRequestActivityExtension; import org.catrobat.catroid.ui.runtimepermissions.RequiresPermissionTask; @@ -90,26 +83,24 @@ import static org.catrobat.catroid.stage.StageListener.SCREENSHOT_AUTOMATIC_FILE_NAME; import static org.catrobat.catroid.stage.TestResult.TEST_RESULT_MESSAGE; -public class StageActivity extends AndroidApplication implements PermissionHandlingActivity { +public class StageActivity extends AndroidApplication implements PermissionHandlingActivity, PermissionAdaptingActivity { public static final String TAG = StageActivity.class.getSimpleName(); public static StageListener stageListener; public static final int REQUEST_START_STAGE = 101; - public static final int ASK_MESSAGE = 0; - public static final int REGISTER_INTENT = 1; - private static final int PERFORM_INTENT = 2; - public static final int REQUEST_PERMISSION = 3; - public static final int SHOW_TOAST = 4; + public static final int REGISTER_INTENT = 0; + private static final int PERFORM_INTENT = 1; + public static final int SHOW_DIALOG = 2; + public static final int SHOW_TOAST = 3; StageAudioFocus stageAudioFocus; PendingIntent pendingIntent; NfcAdapter nfcAdapter; private static NdefMessage nfcTagMessage; StageDialog stageDialog; - private AlertDialog askDialog; - private AlertDialog permissionDialog; + BrickDialogManager brickDialogManager; private boolean resizePossible; static int numberOfSpritesCloned; @@ -152,12 +143,6 @@ protected void onDestroy() { if (ProjectManager.getInstance().getCurrentProject() != null) { StageLifeCycleController.stageDestroy(this); } - if (askDialog != null) { - askDialog.dismiss(); - } - if (permissionDialog != null) { - permissionDialog.dismiss(); - } super.onDestroy(); } @@ -173,103 +158,34 @@ public void handleMessage(Message message) { List params = (ArrayList) message.obj; switch (message.what) { - case ASK_MESSAGE: - showDialog((String) params.get(1), (AskAction) params.get(0)); - break; - case REQUEST_PERMISSION: - askUserForPermission((String) params.get(1), (WebAction) params.get(0)); - break; case REGISTER_INTENT: currentStage.queueIntent((IntentListener) params.get(0)); break; case PERFORM_INTENT: currentStage.startQueuedIntent((Integer) params.get(0)); break; + case SHOW_DIALOG: + brickDialogManager.showDialog((BrickDialogManager.DialogType) params.get(0), + (Action) params.get(1), (String) params.get(2)); + break; case SHOW_TOAST: showToastMessage((String) params.get(0)); break; default: Log.e(TAG, "Unhandled message in messagehandler, case " + message.what); - break; } } }; } - private void showDialog(String question, final AskAction askAction) { - StageLifeCycleController.stagePause(this); - - final EditText edittext = new EditText(getContext()); - - askDialog = new AlertDialog.Builder(new ContextThemeWrapper(this, R.style.Theme_AppCompat_Dialog)) - .setView(edittext) - .setMessage(getContext().getString(R.string.brick_ask_dialog_hint)) - .setTitle(question) - .setCancelable(false) - .setOnKeyListener((dialog, keyCode, event) -> { - if (keyCode == KeyEvent.KEYCODE_BACK) { - onBackPressed(); - return true; - } - return false; - }) - .setPositiveButton(getContext().getString(R.string.brick_ask_dialog_submit), (dialog, whichButton) -> { - String questionAnswer = edittext.getText().toString(); - askAction.setAnswerText(questionAnswer); - }) - .setOnDismissListener(dialog -> { - askDialog = null; - StageLifeCycleController.stageResume(this); - }) - .create(); - - if (askDialog.getWindow() != null) { - askDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - } - askDialog.show(); + public boolean dialogIsShowing() { + return (stageDialog.isShowing() || brickDialogManager.dialogIsShowing()); } private void showToastMessage(String message) { ToastUtil.showError(this, message); } - private void askUserForPermission(String url, final WebAction webAction) { - StageLifeCycleController.stagePause(this); - - final View view = LayoutInflater.from(this).inflate(R.layout.dialog_request_permission, null); - final TextView urlView = view.findViewById(R.id.request_url); - urlView.setText(url); - final TextView warningView = view.findViewById(R.id.request_warning); - warningView.setText(Html.fromHtml( - getString(R.string.brick_web_request_warning_message, Constants.WEB_REQUEST_WIKI_URL))); - warningView.setMovementMethod(LinkMovementMethod.getInstance()); - - permissionDialog = new AlertDialog.Builder(new ContextThemeWrapper(this, R.style.Theme_AppCompat_Dialog)) - .setTitle(getContext().getString(R.string.brick_web_request_warning_title)) - .setCancelable(false) - .setView(view) - .setOnKeyListener((dialog, keyCode, event) -> { - if (keyCode == KeyEvent.KEYCODE_BACK) { - onBackPressed(); - return true; - } - return false; - }) - .setPositiveButton(getContext().getString(R.string.yes), (dialog, whichButton) -> webAction.grantPermission()) - .setNegativeButton(getContext().getString(R.string.no), (dialog, whichButton) -> webAction.denyPermission()) - .setOnDismissListener(dialog -> { - permissionDialog = null; - StageLifeCycleController.stageResume(this); - }) - .create(); - - permissionDialog.show(); - } - - public boolean dialogIsShowing() { - return (stageDialog.isShowing() || askDialog != null || permissionDialog != null); - } - @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); @@ -353,7 +269,7 @@ void calculateScreenSizes() { if (virtualScreenHeight > virtualScreenWidth && isInLandscapeMode() || virtualScreenHeight < virtualScreenWidth && isInPortraitMode()) { - swapWidthAndHeigth(); + swapWidthAndHeight(); } float aspectRatio = (float) virtualScreenWidth / (float) virtualScreenHeight; @@ -394,7 +310,7 @@ private boolean isInLandscapeMode() { return !isInPortraitMode(); } - private void swapWidthAndHeigth() { + private void swapWidthAndHeight() { int tmp = ScreenValues.SCREEN_HEIGHT; ScreenValues.SCREEN_HEIGHT = ScreenValues.SCREEN_WIDTH; ScreenValues.SCREEN_WIDTH = tmp; @@ -482,6 +398,27 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } } + @Override + public void adaptToDeniedPermissions(List deniedPermissions) { + Brick.ResourcesSet requiredResources = new Brick.ResourcesSet(); + Project project = ProjectManager.getInstance().getCurrentProject(); + + for (Scene scene: project.getSceneList()) { + for (Sprite sprite : scene.getSpriteList()) { + for (Brick brick : sprite.getAllBricks()) { + brick.addRequiredResources(requiredResources); + List requiredPermissions = BrickResourcesToRuntimePermissions.translate(requiredResources); + requiredPermissions.retainAll(deniedPermissions); + + if (!requiredPermissions.isEmpty()) { + brick.setCommentedOut(true); + } + requiredResources.clear(); + } + } + } + } + public interface IntentListener { Intent getTargetIntent(); void onIntentResult(int resultCode, Intent data); //don't do heavy processing here diff --git a/catroid/src/main/java/org/catrobat/catroid/stage/StageLifeCycleController.java b/catroid/src/main/java/org/catrobat/catroid/stage/StageLifeCycleController.java index adffd13876b..dab217aeafd 100644 --- a/catroid/src/main/java/org/catrobat/catroid/stage/StageLifeCycleController.java +++ b/catroid/src/main/java/org/catrobat/catroid/stage/StageLifeCycleController.java @@ -90,8 +90,9 @@ static void stageCreate(final StageActivity stageActivity) { stageActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - stageActivity.stageListener = new StageListener(); - stageActivity.stageDialog = new StageDialog(stageActivity, stageActivity.stageListener, R.style.StageDialog); + StageActivity.stageListener = new StageListener(); + stageActivity.stageDialog = new StageDialog(stageActivity, StageActivity.stageListener, R.style.StageDialog); + stageActivity.brickDialogManager = new BrickDialogManager(stageActivity); stageActivity.calculateScreenSizes(); stageActivity.configuration = new AndroidApplicationConfiguration(); @@ -101,9 +102,9 @@ static void stageCreate(final StageActivity stageActivity) { stageActivity.setContentView(R.layout.activity_stage_gamepad); CastManager.getInstance().initializeGamepadActivity(stageActivity); CastManager.getInstance() - .addStageViewToLayout((GLSurfaceView20) stageActivity.initializeForView(stageActivity.stageListener, stageActivity.configuration)); + .addStageViewToLayout((GLSurfaceView20) stageActivity.initializeForView(StageActivity.stageListener, stageActivity.configuration)); } else { - stageActivity.initialize(stageActivity.stageListener, stageActivity.configuration); + stageActivity.initialize(StageActivity.stageListener, stageActivity.configuration); } //CATROID-105 - TODO: does this make any difference? probably necessary for cast: @@ -241,6 +242,7 @@ public static void stageResume(final StageActivity stageActivity) { static void stageDestroy(StageActivity stageActivity) { if (checkPermission(stageActivity, getProjectsRuntimePermissionList())) { + stageActivity.brickDialogManager.dismissAllDialogs(); stageActivity.jumpingSumoDisconnect(); BluetoothDeviceService service = ServiceProvider.getService(CatroidService.BLUETOOTH_DEVICE_SERVICE); if (service != null) { diff --git a/catroid/src/main/java/org/catrobat/catroid/stage/StageListener.java b/catroid/src/main/java/org/catrobat/catroid/stage/StageListener.java index 7cf236ef9b8..75389cf078e 100644 --- a/catroid/src/main/java/org/catrobat/catroid/stage/StageListener.java +++ b/catroid/src/main/java/org/catrobat/catroid/stage/StageListener.java @@ -59,6 +59,7 @@ import org.catrobat.catroid.content.Look; import org.catrobat.catroid.content.Project; import org.catrobat.catroid.content.Scene; +import org.catrobat.catroid.content.SoundBackup; import org.catrobat.catroid.content.Sprite; import org.catrobat.catroid.content.XmlHeader; import org.catrobat.catroid.content.eventids.EventId; @@ -815,7 +816,7 @@ private class StageBackup { PenActor penActor; EmbroideryPatternManager embroideryPatternManager; Map bubbleActorMap; - Map soundsDurationMap; + List soundBackupList; boolean paused; boolean finished; @@ -869,7 +870,8 @@ private StageBackup saveToBackup() { CameraManager.getInstance().pauseForScene(); } } - backup.soundsDurationMap = SoundManager.getInstance().getPlayingSoundDurationMap(); + backup.soundBackupList = new ArrayList<>(); + backup.soundBackupList.addAll(SoundManager.getInstance().getPlayingSoundBackups()); return backup; } @@ -912,8 +914,9 @@ private void restoreFromBackup(StageBackup backup) { if (CameraManager.getInstance() != null && backup.cameraRunning) { CameraManager.getInstance().resumeForScene(); } - for (Map.Entry entry : backup.soundsDurationMap.entrySet()) { - SoundManager.getInstance().playSoundFileWithStartTime(entry.getKey(), entry.getValue()); + for (SoundBackup soundBackup : backup.soundBackupList) { + SoundManager.getInstance().playSoundFileWithStartTime(soundBackup.getPathToSoundFile(), + soundBackup.getStartedBySprite(), soundBackup.getCurrentPosition()); } initStageInputListener(); } diff --git a/catroid/src/main/java/org/catrobat/catroid/stage/StageResourceHolder.java b/catroid/src/main/java/org/catrobat/catroid/stage/StageResourceHolder.java index c91d06d7dda..ce3f02a31a8 100644 --- a/catroid/src/main/java/org/catrobat/catroid/stage/StageResourceHolder.java +++ b/catroid/src/main/java/org/catrobat/catroid/stage/StageResourceHolder.java @@ -49,6 +49,7 @@ import org.catrobat.catroid.camera.CameraManager; import org.catrobat.catroid.cast.CastManager; import org.catrobat.catroid.common.CatroidService; +import org.catrobat.catroid.common.Constants; import org.catrobat.catroid.common.ServiceProvider; import org.catrobat.catroid.content.Project; import org.catrobat.catroid.content.bricks.Brick; @@ -71,6 +72,7 @@ import org.catrobat.catroid.utils.Utils; import org.catrobat.catroid.utils.VibrationUtil; +import java.io.File; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -402,6 +404,20 @@ public void onClick(DialogInterface dialog, int id) { connectRaspberrySocket(); } + if (requiredResourcesSet.contains(Brick.STORAGE_WRITE)) { + File directory = Constants.EXTERNAL_STORAGE_ROOT_EXPORT_DIRECTORY; + + if (directory.exists() || directory.mkdirs()) { + resourceInitialized(); + } else { + resourceFailed(Brick.STORAGE_WRITE); + } + } + + if (requiredResourcesSet.contains(Brick.STORAGE_READ)) { + resourceInitialized(); + } + if (initFinished()) { initFinishedRunStage(); } diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/ScratchProgramDetailsActivity.java b/catroid/src/main/java/org/catrobat/catroid/ui/ScratchProgramDetailsActivity.java index bd0e78140fa..8e3a9675db0 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/ScratchProgramDetailsActivity.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/ScratchProgramDetailsActivity.java @@ -63,10 +63,9 @@ import java.util.Locale; import androidx.annotation.NonNull; -import androidx.appcompat.widget.Toolbar; import androidx.recyclerview.widget.RecyclerView; -import static org.catrobat.catroid.utils.NumberFormats.humanFriendlyFormattedShortNumber; +import static org.catrobat.catroid.utils.NumberFormats.toMetricUnitRepresentation; public class ScratchProgramDetailsActivity extends BaseActivity implements FetchScratchProgramDetailsTask.ScratchProgramListTaskDelegate, @@ -95,7 +94,7 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_scratch_project_details); - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); + setSupportActionBar(findViewById(R.id.toolbar)); getSupportActionBar().setDisplayHomeAsUpEnabled(true); String scratchConverter = getString(R.string.main_menu_scratch_converter); SpannableString scratchConverterBeta = new SpannableString(scratchConverter @@ -270,11 +269,11 @@ private void onProgramDataUpdated() { } ((TextView) findViewById(R.id.scratch_project_favorites_text)) - .setText(humanFriendlyFormattedShortNumber(programData.getFavorites())); + .setText(toMetricUnitRepresentation(programData.getFavorites())); ((TextView) findViewById(R.id.scratch_project_loves_text)) - .setText(humanFriendlyFormattedShortNumber(programData.getLoves())); + .setText(toMetricUnitRepresentation(programData.getLoves())); ((TextView) findViewById(R.id.scratch_project_views_text)) - .setText(humanFriendlyFormattedShortNumber(programData.getViews())); + .setText(toMetricUnitRepresentation(programData.getViews())); TextView dateSharedView = findViewById(R.id.date_shared_view); TextView dateModifiedView = findViewById(R.id.date_modified_view); diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/WebViewActivity.java b/catroid/src/main/java/org/catrobat/catroid/ui/WebViewActivity.java index 099e79ffa4b..3d267dff0d5 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/WebViewActivity.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/WebViewActivity.java @@ -158,7 +158,8 @@ public void onPageStarted(WebView view, String urlClient, Bitmap favicon) { webViewLoadingDialog.setCanceledOnTouchOutside(false); webViewLoadingDialog.setProgressStyle(android.R.style.Widget_ProgressBar_Small); webViewLoadingDialog.show(); - } else if (allowGoBack && urlClient.equals(FlavoredConstants.BASE_URL_HTTPS)) { + } else if (allowGoBack && (urlClient.equals(FlavoredConstants.BASE_URL_HTTPS) + || urlClient.equals(Constants.BASE_APP_URL_HTTPS))) { allowGoBack = false; onBackPressed(); } diff --git a/catroid/src/main/java/org/catrobat/catroid/ui/controller/BackpackListManager.java b/catroid/src/main/java/org/catrobat/catroid/ui/controller/BackpackListManager.java index bdb594f9000..9541cedfd0c 100644 --- a/catroid/src/main/java/org/catrobat/catroid/ui/controller/BackpackListManager.java +++ b/catroid/src/main/java/org/catrobat/catroid/ui/controller/BackpackListManager.java @@ -29,6 +29,7 @@ import org.catrobat.catroid.content.Scene; import org.catrobat.catroid.content.Script; import org.catrobat.catroid.content.Sprite; +import org.catrobat.catroid.content.bricks.UserDefinedBrick; import org.catrobat.catroid.io.BackpackSerializer; import java.io.File; @@ -100,10 +101,18 @@ public HashMap> getBackpackedScripts() { return getBackpack().backpackedScripts; } + public HashMap> getBackpackedUserDefinedBricks() { + return getBackpack().backpackedUserDefinedBricks; + } + public void addScriptToBackPack(String scriptGroup, List