From c9f182ebf5f35785e23018f18a06a1f50972bfd4 Mon Sep 17 00:00:00 2001
From: Elvin Holst <elvho551@student.liu.se>
Date: Fri, 21 Feb 2025 09:22:09 +0000
Subject: [PATCH] Fix loop block drag behaviour to ensure loop begin/end are
 draggable and come in correct order

---
 README.md                                     | 12 +--
 android/.idea/android.iml                     |  9 +++
 android/.idea/runConfigurations.xml           | 17 ++++
 .../com/example/aida/pages/SequenceTab.kt     |  7 +-
 .../SequenceTabComposables/ActionsData.kt     |  1 -
 .../SequenceTabComposables/SequenceBar.kt     | 80 ++++++++++++++++---
 6 files changed, 105 insertions(+), 21 deletions(-)
 create mode 100644 android/.idea/android.iml
 create mode 100644 android/.idea/runConfigurations.xml

diff --git a/README.md b/README.md
index 28040c7b..15df4f8c 100644
--- a/README.md
+++ b/README.md
@@ -38,8 +38,8 @@ error: failed to push some refs to 'https://github.com/eliasjlara/PUM-04.git'
 This is a safety precaution to keep bugs and non-approved code from the most important branch, the main one! To start coding on a ticket, you can follow these steps as a guide: 
 
 #### Steps on your local machine
-1. Check that you're on the main branch using `git branch`, if you're not, do `git checkout main`.
-2. Pull the latest changes from main using `git pull`.
+1. Check that you're on the Development branch using `git branch`, if you're not, do `git checkout Development`.
+2. Pull the latest changes from Development using `git pull`.
 3. Create a branch with a name relating to the ticket name using `git checkout -b <good-name>`.
 4. Implement your changes on this branch.
 5. When ready, push your changes following standard procedure: 
@@ -66,10 +66,10 @@ Navigate over to the link in the message, or if this isn't the first time you pu
 2. You should now be directed to a page that looks like this: <img width="1065" alt="Update README md #10" src="https://github.com/eliasjlara/PUM-04/assets/94451739/169fc8a6-ec10-4fd4-94ba-7da0d680f1ca">
    Congratulations, you have now created a PR! This is where all updates added to the branch will be shown. If you continue  working on the branch, for example if you forgot a bug, each commit will be shown on the timeline, creating a simple way  to keep track of everything that has happened. Also, notice the big red crosses, don't be alarmed, we will take care of this in the next step.
 3. Before we can merge this pull request we first need someone else to look at it, this is the safety precaution previously mentioned. To request a review from someone you either click on the cogwheel that belongs to the reviewers tab on the to right of the page, and choose a specific person to review the PR. The easiest way however would probably just be to send the link in discord and ask for a review.
-4. Once the PR is reviewed and approved, you can now go ahead and merge the PR into main!
+4. Once the PR is reviewed and approved, you can now go ahead and merge the PR into Development!
 5. After merging you might be given an option to delete the branch, I would recommend that you do it to keep branches clean but this is up to you. 
 
-Wow, your code feature is now real since it's implemented on main 🥳🎉. This should be it for someone who is developing, if someone asked you to review a PR, check out the ["Approve a pull request" section](#approve-a-pull-request).
+Wow, your code feature is now real since it's implemented on Development 🥳🎉. This should be it for someone who is developing, if someone asked you to review a PR, check out the ["Approve a pull request" section](#approve-a-pull-request).
 
 ---
 
@@ -82,12 +82,12 @@ TODO: Write the rest about our iterative development
 
 ### Approve a pull request
 So, someone has asked you to review a pull request... well, don't worry. Here is a guide on how to do it step by step.
-1. First off, reviewing a pr isn't anything special, you simply want to check that all the code that the author wants to merge into main looks good. The simplest way to check is using the `Files changed` tab on the pr page. When actually checking the code, there is no specific way to do this so go ahead and do it in what ever way is best for you, BUT, be thorough, we don't want any errors in main just because you approved the pull request without looking!
+1. First off, reviewing a pr isn't anything special, you simply want to check that all the code that the author wants to merge into Development looks good. The simplest way to check is using the `Files changed` tab on the pr page. When actually checking the code, there is no specific way to do this so go ahead and do it in what ever way is best for you, BUT, be thorough, we don't want any errors in Development just because you approved the pull request without looking!
 2. If there are any comments you want to add:
    - The simplest way to add a comment is to click the line where you have a comment and then simply write the comment and post it.
    - After the author of the pull request has changed or answered your comments, and it now looks good to you, press the `resolve conversation` button to "remove" the comment.
 3. When the pr looks good. Head over to the `Files changed` tab again and press the `Review changes` button, select `Approve` and then submit the review.
-4. The pr should now be reviewed and the author can merge it into main.
+4. The pr should now be reviewed and the author can merge it into Development.
 
 <br/>
 
diff --git a/android/.idea/android.iml b/android/.idea/android.iml
new file mode 100644
index 00000000..d6ebd480
--- /dev/null
+++ b/android/.idea/android.iml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/android/.idea/runConfigurations.xml b/android/.idea/runConfigurations.xml
new file mode 100644
index 00000000..16660f1d
--- /dev/null
+++ b/android/.idea/runConfigurations.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RunConfigurationProducerService">
+    <option name="ignoredProducers">
+      <set>
+        <option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
+        <option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
+        <option value="com.intellij.execution.junit.PatternConfigurationProducer" />
+        <option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
+        <option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
+        <option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
+        <option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
+        <option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
+      </set>
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/android/app/src/main/java/com/example/aida/pages/SequenceTab.kt b/android/app/src/main/java/com/example/aida/pages/SequenceTab.kt
index f5aeaf0a..1ae9fa3d 100644
--- a/android/app/src/main/java/com/example/aida/pages/SequenceTab.kt
+++ b/android/app/src/main/java/com/example/aida/pages/SequenceTab.kt
@@ -683,6 +683,7 @@ fun UserButtons(
                                     else
                                     {
                                         // Execute next action
+
                                         if((curr - 1) < actionsInSequenceBar.size)
                                         {
                                             // Sends action to ROS
@@ -700,6 +701,7 @@ fun UserButtons(
                                         // If loop start encountered
                                         if (action.name == "LOOP START")
                                         {
+                                            println("IN loop Start")
                                             if (!isLooping)
                                             {
                                                 loopCounter = (action.data.value).toInt()
@@ -810,8 +812,9 @@ private fun addActionToSequenceBar(action: BaseAction, actionsInSequenceBar: Mut
 
     if (action.name == "LOOP") {
         // For LOOP, add LOOP START and LOOP END together
-        val loopStart = SpecialAction(id = 2, name = "LOOP START", isActive = mutableStateOf(true), isReorderable = false, data = mutableStateOf("1"), icon = mutableStateOf(Icons.Outlined.Repeat), cardColor = mutableStateOf(specialActionColor), duration = 5.0f)
-        val loopEnd = SpecialAction(id = 2, name = "LOOP END", isActive = mutableStateOf(true), isReorderable = false, data = mutableStateOf(""), icon = mutableStateOf(Icons.Outlined.Repeat), cardColor = mutableStateOf(specialActionColor), duration = 1.0f)
+        // TODO: LOOP setting action settings are defind in two seperet areas HERE and in ActionsData.kt
+        val loopStart = SpecialAction(id = 2, name = "LOOP START", isActive = mutableStateOf(true), isReorderable = true, data = mutableStateOf("1"), icon = mutableStateOf(Icons.Outlined.Repeat), cardColor = mutableStateOf(specialActionColor), duration = 5.0f)
+        val loopEnd = SpecialAction(id = 2, name = "LOOP END", isActive = mutableStateOf(true), isReorderable = true, data = mutableStateOf(""), icon = mutableStateOf(Icons.Outlined.Repeat), cardColor = mutableStateOf(specialActionColor), duration = 1.0f)
 
         val copiedLoopStart = loopStart.copy(data = mutableStateOf(loopStart.data.value), icon = mutableStateOf(loopStart.icon.value))
         val copiedLoopEnd = loopEnd.copy(data = mutableStateOf(loopEnd.data.value), icon = mutableStateOf(loopEnd.icon.value))
diff --git a/android/app/src/main/java/com/example/aida/ui/composables/SequenceTabComposables/ActionsData.kt b/android/app/src/main/java/com/example/aida/ui/composables/SequenceTabComposables/ActionsData.kt
index d667cd6b..9bbbc3b5 100644
--- a/android/app/src/main/java/com/example/aida/ui/composables/SequenceTabComposables/ActionsData.kt
+++ b/android/app/src/main/java/com/example/aida/ui/composables/SequenceTabComposables/ActionsData.kt
@@ -16,7 +16,6 @@ val moveActionColor = Color(0xff6c779c)    // Color for move actions
 val specialActionColor = Color(0xff435159) // Color for special actions
 val activeColor = Color(0xFF5B7A8C)        // Color indicating an active state
 val inActiveColor = Color(0xff8A8A8A)      // Color indicating an inactive state
-
 /**
  * Represents the base attributes of an action.
  *
diff --git a/android/app/src/main/java/com/example/aida/ui/composables/SequenceTabComposables/SequenceBar.kt b/android/app/src/main/java/com/example/aida/ui/composables/SequenceTabComposables/SequenceBar.kt
index b865ae79..a3e182cf 100644
--- a/android/app/src/main/java/com/example/aida/ui/composables/SequenceTabComposables/SequenceBar.kt
+++ b/android/app/src/main/java/com/example/aida/ui/composables/SequenceTabComposables/SequenceBar.kt
@@ -33,7 +33,6 @@ import androidx.compose.runtime.key
 import androidx.compose.runtime.mutableLongStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.snapshots.SnapshotStateList
-import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
@@ -64,7 +63,6 @@ import com.example.aida.popups.PopupLoop
 import androidx.compose.ui.platform.LocalContext
 import com.example.aida.viewmodels.MainViewModel
 import java.util.Locale
-
 /**
  * Displays a horizontal "sequence bar" where users can arrange and configure actions.
  *
@@ -222,6 +220,8 @@ fun SequenceBar(
                 .zIndex(2f)
         )
 
+
+
         // Reorderable row of actions
         // Allows drag-and-drop reordering if items permit it.
         ReorderableRow(
@@ -235,13 +235,17 @@ fun SequenceBar(
             horizontalArrangement = Arrangement.spacedBy(12 .dp),
             list = actionsInSequenceBar.toList(),
             onSettle = { fromIndex, toIndex ->
-                val fromItem = actionsInSequenceBar[fromIndex]
-
-                // Only reorder if the action allows reordering
-                if (fromItem.isReorderable) {
-                    actionsInSequenceBar.add(toIndex, actionsInSequenceBar.removeAt(fromIndex))
-                } else {
-                    println("Action is not reorderable, no action taken.")
+                if (fromIndex in actionsInSequenceBar.indices && toIndex in actionsInSequenceBar.indices) {
+                    val fromItem = actionsInSequenceBar[fromIndex]
+                    // Only reorder if the action allows reordering
+                    if (fromItem.isReorderable) {
+                        actionsInSequenceBar.add(
+                            toIndex,
+                            actionsInSequenceBar.removeAt(fromIndex)
+                        )
+                    }else {
+                        Log.e("SequenceBar, onSettle", "Error: fromIndex or to Index out of bounds in onSettle.")
+                    }
                 }
             },
             onMove = {
@@ -279,6 +283,7 @@ fun SequenceBar(
                                                 index - 1,
                                                 actionsInSequenceBar.removeAt(index)
                                             )
+
                                             true
                                         } else {
                                             false
@@ -293,6 +298,7 @@ fun SequenceBar(
                                                 index + 1,
                                                 actionsInSequenceBar.removeAt(index)
                                             )
+
                                             true
                                         } else {
                                             false
@@ -434,8 +440,6 @@ fun SequenceBar(
                                     textAlign = TextAlign.Center,
                                     fontWeight = FontWeight.Bold,
                                 )
-
-
                             }
                         }
                         // If this is a special action (except LOOP END), show a settings button to open popup
@@ -491,6 +495,8 @@ fun SequenceBar(
             }
         }
     }
+
+    sortLoopButtons(actionsInSequenceBar)
 }
 
 /**
@@ -591,7 +597,7 @@ fun TimeLine()
                 .fillMaxHeight()
                 .width(160.dp)
                 .background(Color(0x4D099D0B))
-        ) {}
+        )
     }
 }
 
@@ -718,5 +724,55 @@ fun DurationCountdown(
 }
 
 
+/**
+ * Sorts the sequence to ensure that loop ends don't occur before their corresponding loop begin.
+ *
+ * @param actionsInSequenceBar A mutable list (state) of actions to sort.
+ */
+private fun sortLoopButtons(actionsInSequenceBar: SnapshotStateList<BaseAction>) {
+    var loopBeginIndex = 0 // Current left index
+    var loopEndIndex = actionsInSequenceBar.size - 1 // Current right index
+    var counterBegin = 0 // Keeps track of loop starts encountered
+    var counterEnd = 0 // Keeps track of loop ends encountered
+
+    // Keep 2 indices, iterate from both ends until they reach eachother
+    while (loopBeginIndex < loopEndIndex) {
+        if (actionsInSequenceBar[loopBeginIndex].name == "LOOP END" && actionsInSequenceBar[loopEndIndex].name == "LOOP START") {
+            if (counterBegin == 0 && counterEnd == 0) {
+                // Encountered a loop begin and corresponding end in incorrect order, swap them
+                // TODO: Check why add does not do the expected effect
+                val temp = actionsInSequenceBar[loopBeginIndex]
+                actionsInSequenceBar[loopBeginIndex] = actionsInSequenceBar[loopEndIndex]
+                actionsInSequenceBar[loopEndIndex] = temp
+            }
+
+            if (counterBegin != 0) {
+                loopBeginIndex++
+                counterBegin--
+            }
+
+            if (counterEnd != 0) {
+                loopEndIndex--
+                counterEnd--
+            }
+        }
 
+        // If the left index has encountered a loop end, stay there, otherwise keep going
+        if (actionsInSequenceBar[loopBeginIndex].name != "LOOP END") {
+            // If the left index is on a loop begin, increment  the loop begin counter
+            if (actionsInSequenceBar[loopBeginIndex].name == "LOOP START")
+                counterBegin++
 
+            loopBeginIndex++
+        }
+
+        // If the right index has encountered a loop start, stay there, otherwise keep going
+        if (actionsInSequenceBar[loopEndIndex].name != "LOOP START") {
+            // If the right index is on a loop end, increment  the loop begin counter
+            if (actionsInSequenceBar[loopEndIndex].name == "LOOP END")
+                counterEnd++
+
+            loopEndIndex--
+        }
+    }
+}
-- 
GitLab