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 0c581228924e8682984915f2822a588ec01c59f6..5ae01840394f027a2ea13b8506feaf0cbafac8c1 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 @@ -58,6 +58,7 @@ import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.tween import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.animateColorAsState +import androidx.compose.runtime.mutableDoubleStateOf import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.delay @@ -717,8 +718,6 @@ fun UserButtons( { loopCounter-- curr = loopStartIndex - - println("should jump back to " + curr ) } else @@ -815,11 +814,11 @@ private fun addActionToSequenceBar(action: BaseAction, actionsInSequenceBar: Mut if (action.name == "LOOP") { // For LOOP, add LOOP START and LOOP END together // 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 = 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 = 0f) + 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 = 0f, actionScale = mutableDoubleStateOf(1.0)) + val loopEnd = SpecialAction(id = 2, name = "LOOP END", isActive = mutableStateOf(true), isReorderable = true, data = mutableStateOf(""), icon = mutableStateOf(Icons.Outlined.Repeat), cardColor = mutableStateOf(specialActionColor), duration = 0f, actionScale = mutableDoubleStateOf(1.0)) - 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)) + val copiedLoopStart = loopStart.clone() + val copiedLoopEnd = loopEnd.clone() actionsInSequenceBar.add(copiedLoopStart) actionsInSequenceBar.add(copiedLoopEnd) @@ -828,12 +827,8 @@ private fun addActionToSequenceBar(action: BaseAction, actionsInSequenceBar: Mut { // For normal actions, create a copy with fresh mutable states so each action is independent val copiedAction = when (action) { - is MoveAction -> action.copy(data = mutableStateOf(action.data.value), remainingDuration = mutableFloatStateOf(action.remainingDuration.value)) - is SpecialAction -> action.copy( - data = mutableStateOf(action.data.value), - cardColor = mutableStateOf(action.cardColor.value), - icon = mutableStateOf(action.icon.value) - ) + is MoveAction -> action.clone() + is SpecialAction -> action.clone() else -> action } @@ -887,9 +882,12 @@ private suspend fun executeActionWithCountdown( ) { // Reset duration at start of execution action.remainingDuration.value = action.duration + action.width.value = 210.dp + action.height.value = 200.dp + action.actionScale.value = 1.6 // Start countdown in parallel with action execution - scope.launch { + val thred = scope.launch { while (action.remainingDuration.value > 0) { delay(100) // Update every 100ms action.remainingDuration.value = (action.remainingDuration.value - 0.1f) @@ -904,5 +902,11 @@ private suspend fun executeActionWithCountdown( } // Wait for the full duration - delay((action.duration * 1000).toLong()) + //delay((action.duration * 1000).toLong()) + thred.join() + //delay(1000) + action.width.value = 160.dp + action.height.value = 150.dp + action.actionScale.value = 1.0 + } \ No newline at end of file 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 9a0372fdb3e3b802f40b4afa50666392b4215c9f..a1bfb81c1f95ed4f150a50490c4293678b83ff0c 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 @@ -5,9 +5,12 @@ import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.* +import androidx.compose.runtime.mutableDoubleStateOf import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp /** * Defines a set of colors used for action components. @@ -25,7 +28,7 @@ val inActiveColor = Color(0xff8A8A8A) // Color indicating an inactive state * - `MoveAction` instances are active by default. * - `SpecialAction` instances are inactive by default, except for the "Loop" action, which starts as active. */ -interface BaseAction { +interface BaseAction: Cloneable { val id: Int // A unique identifier for each action val name: String // The name of the action var duration: Float // Expected duration of action @@ -34,10 +37,13 @@ interface BaseAction { var isReorderable: Boolean // Determines if the action can be reordered var data: MutableState<String> // Stores data related to the action var icon: MutableState<ImageVector> // The icon displayed for the action - var cardColor: MutableState<Color> // The color of the action's visual container -} - + var cardColor: MutableState<Color> // The color of the action's visual container + var height: MutableState<Dp> + var width: MutableState<Dp> + var actionScale: MutableState<Double> + public override fun clone(): BaseAction + } /** * These data classes implement `BaseAction` and can define their own specific attributes. */ @@ -50,8 +56,29 @@ data class MoveAction( override var remainingDuration: MutableState<Float> = mutableFloatStateOf(duration), override var data: MutableState<String>, override var icon: MutableState<ImageVector>, - override var cardColor: MutableState<Color> = mutableStateOf(moveActionColor) // Default value, used unless explicitly overridden -) : BaseAction + override var cardColor: MutableState<Color> = mutableStateOf(moveActionColor), // Default value, used unless explicitly overridden + override var height: MutableState<Dp> = mutableStateOf(150.dp), + override var width: MutableState<Dp> = mutableStateOf(160.dp), + override var actionScale: MutableState<Double> = mutableDoubleStateOf(1.0) +) : BaseAction { + + override fun clone(): MoveAction { + return MoveAction( + id = this.id, + name = this.name, + isActive = mutableStateOf(this.isActive.value), + isReorderable = this.isReorderable, + duration = this.duration, + remainingDuration = mutableFloatStateOf(this.remainingDuration.value), + data = mutableStateOf(this.data.value), + icon = mutableStateOf(this.icon.value), + cardColor = mutableStateOf(this.cardColor.value), + height = mutableStateOf(this.height.value), + width = mutableStateOf(this.width.value), + actionScale = mutableDoubleStateOf(this.actionScale.value) + ) + } +} data class SpecialAction( override val id: Int, @@ -63,8 +90,30 @@ data class SpecialAction( override var data: MutableState<String>, override var icon: MutableState<ImageVector>, override var cardColor: MutableState<Color> = mutableStateOf(specialActionColor), // Default value, used unless explicitly overridden + override var height: MutableState<Dp> = mutableStateOf(150.dp), + override var width: MutableState<Dp> = mutableStateOf(160.dp), + override var actionScale: MutableState<Double> = mutableDoubleStateOf(1.0), val iconRotation: MutableState<Float> = mutableStateOf(0f) -) : BaseAction +) : BaseAction { + + override fun clone(): SpecialAction { + return SpecialAction( + id = this.id, + name = this.name, + duration = this.duration, + remainingDuration = mutableFloatStateOf(this.remainingDuration.value), + isActive = mutableStateOf(this.isActive.value), + isReorderable = this.isReorderable, + data = mutableStateOf(this.data.value), + icon = mutableStateOf(this.icon.value), + cardColor = mutableStateOf(this.cardColor.value), + height = mutableStateOf(this.height.value), + width = mutableStateOf(this.width.value), + actionScale = mutableDoubleStateOf(this.actionScale.value), + iconRotation = mutableStateOf(this.iconRotation.value) + ) + } +} /** 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 c79e87c203c78c08d83b051a9fce88e1c6401608..63cd7d984ddf30d02ec5ec6fb8957b035ec40ff8 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 @@ -5,7 +5,10 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import sh.calvin.reorderable.ReorderableRow import androidx.compose.animation.* +import androidx.compose.animation.core.EaseInOut import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas import androidx.compose.foundation.ScrollState @@ -267,135 +270,146 @@ fun SequenceBar( key(item.id) { val lastClickTime = remember { mutableLongStateOf(0L) } // Each action is represented as a card: - Card( - modifier = Modifier - .width(160.dp) - .height(150.dp) - .clip(RoundedCornerShape(12.dp)) - .background(item.cardColor.value) - .clickable { - // Detect double-click to open the popup: - val currentTime = System.currentTimeMillis() - if (currentTime - lastClickTime.longValue < 500) { - println("Action pressed: " + item::class.simpleName) - togglePopUp = true - selectedActionIndex = index - } - lastClickTime.longValue = currentTime - } - .semantics { - // Accessibility custom actions for moving items left or right - customActions = listOf( - CustomAccessibilityAction( - label = "Move Left", - action = { - if (index > 0) { - actionsInSequenceBar.add( - index - 1, - actionsInSequenceBar.removeAt(index) - ) - true - } else { - false + AnimatedVisibility( + visible = true, + enter = scaleIn(), + exit = scaleOut() + ) { + Card( + modifier = Modifier + .animateContentSize( + animationSpec = tween(durationMillis = 250, easing = EaseInOut), + alignment = Alignment.Center + ) + .width(item.width.value) + .height(item.height.value) + .clip(RoundedCornerShape(12.dp)) + .background(item.cardColor.value) + .clickable { + // Detect double-click to open the popup: + val currentTime = System.currentTimeMillis() + if (currentTime - lastClickTime.longValue < 500) { + println("Action pressed: " + item::class.simpleName) + togglePopUp = true + selectedActionIndex = index + } + lastClickTime.longValue = currentTime + } + .semantics { + // Accessibility custom actions for moving items left or right + customActions = listOf( + CustomAccessibilityAction( + label = "Move Left", + action = { + if (index > 0) { + actionsInSequenceBar.add( + index - 1, + actionsInSequenceBar.removeAt(index) + ) + + true + } else { + false + } } - } - ), - CustomAccessibilityAction( - label = "Move Right", - action = { - if (index < actionsInSequenceBar.size - 1) { - actionsInSequenceBar.add( - index + 1, - actionsInSequenceBar.removeAt(index) - ) - - true - } else { - false + ), + CustomAccessibilityAction( + label = "Move Right", + action = { + if (index < actionsInSequenceBar.size - 1) { + actionsInSequenceBar.add( + index + 1, + actionsInSequenceBar.removeAt(index) + ) + + true + } else { + false + } } - } - ), - ) - } - - // If the item can be reordered, attach a draggable handle - .then( - if (item.isReorderable) { - Modifier.draggableHandle( - onDragStarted = { - haptic.performHapticFeedback(HapticFeedbackType.LongPress) - }, - onDragStopped = { - haptic.performHapticFeedback(HapticFeedbackType.LongPress) - }, - interactionSource = interactionSource + ), ) - } else { - Modifier } - ), - colors = CardDefaults.cardColors(containerColor = item.cardColor.value) - ) { - // Main container inside an action block - // Content inside each action's card: - Box( - modifier = Modifier - .fillMaxSize(), - contentAlignment = Alignment.Center + + // If the item can be reordered, attach a draggable handle + .then( + if (item.isReorderable) { + Modifier.draggableHandle( + onDragStarted = { + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + }, + onDragStopped = { + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + }, + interactionSource = interactionSource + ) + } else { + Modifier + } + ), + colors = CardDefaults.cardColors(containerColor = item.cardColor.value) ) { - Column( + // Main container inside an action block + // Content inside each action's card: + Box( modifier = Modifier - .fillMaxSize() - .padding(top = 10.dp, start = 5.dp, end = 5.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.SpaceEvenly, - ){ - // Different layouts for special cases like LOOP START/END - if (item.name == "LOOP START" || item.name == "LOOP END") - { - Column( - modifier = Modifier - .fillMaxSize() - .padding(bottom = 18.dp), - horizontalAlignment = Alignment.CenterHorizontally - ){ - Box( + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(top = 10.dp, start = 5.dp, end = 5.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceEvenly, + ) { + // Different layouts for special cases like LOOP START/END + if (item.name == "LOOP START" || item.name == "LOOP END") { + Column( modifier = Modifier - .weight(1f), - contentAlignment = Alignment.Center - ){ - // Icon and loop parameter (e.g. number of loops) + .fillMaxSize() + .padding(bottom = 18.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box( + modifier = Modifier + .weight(1f), + contentAlignment = Alignment.Center + ) { + // Icon and loop parameter (e.g. number of loops) Icon( imageVector = item.icon.value, contentDescription = "", tint = Color.White, modifier = Modifier - .size(80.dp) - ) - if(item.name != "LOOP END") - { - Text( - text = minimizeText(item.data.value, maxLength = 15), - color = Color.White, - textAlign = TextAlign.Center, - fontWeight = FontWeight.Bold, - fontSize = 20.sp + .size((80 * item.actionScale.value).dp) ) + if (item.name != "LOOP END") { + Text( + text = minimizeText( + item.data.value, + maxLength = 15 + ), + color = Color.White, + textAlign = TextAlign.Center, + fontWeight = FontWeight.Bold, + fontSize = (20 * item.actionScale.value).sp + ) + } } - } - Text( - text = item.name, - color = Color.White, - fontWeight = FontWeight.Bold, - style = TextStyle( - fontSize = 15.sp, - textAlign = TextAlign.Center + Text( + text = item.name, + color = Color.White, + fontWeight = FontWeight.Bold, + style = TextStyle( + fontSize = (15 * item.actionScale.value).sp, + textAlign = TextAlign.Center + ) ) - ) + } } - } // else if (item.name == "LOOP END") // { // Text( @@ -408,99 +422,109 @@ fun SequenceBar( // ) // ) // } - else if (item::class.simpleName == "SpecialAction") - { - // Display the special action icon and data (e.g., a gesture name) - Icon( - imageVector = item.icon.value, - contentDescription = "", - tint = Color.White, - modifier = Modifier - .size(70.dp) - .graphicsLayer( - rotationZ = if (item.data.value == "Finger gun") 90f else 0f - ) - ) - Text( - text = minimizeText(item.data.value, maxLength = 15), - color = Color.White, - textAlign = TextAlign.Center, - fontWeight = FontWeight.Bold, - ) - } - else { - // Default case for normal actions: show icon and name - Text( - text = String.format(Locale.getDefault(), "%.2f", item.remainingDuration.value), - color = Color.White, - fontWeight = FontWeight.Bold, - textAlign = TextAlign.Start, + else if (item::class.simpleName == "SpecialAction") { + // Display the special action icon and data (e.g., a gesture name) + Icon( + imageVector = item.icon.value, + contentDescription = "", + tint = Color.White, + modifier = Modifier + .size((70 * item.actionScale.value).dp) + .graphicsLayer( + rotationZ = if (item.data.value == "Finger gun") 90f else 0f + ) + ) + Text( + text = minimizeText(item.data.value, maxLength = 15), + color = Color.White, + textAlign = TextAlign.Center, + fontWeight = FontWeight.Bold, + fontSize = (20 * item.actionScale.value).sp + ) + } else { + // Default case for normal actions: show icon and name + Text( + text = String.format( + Locale.getDefault(), + "%.2f", + item.remainingDuration.value + ), + color = Color.White, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Start, + fontSize = (15 * item.actionScale.value).sp ) - Icon( - imageVector = item.icon.value, - contentDescription = "", - tint = Color.White, + Icon( + imageVector = item.icon.value, + contentDescription = "", + tint = Color.White, + modifier = Modifier + .size((70 * item.actionScale.value).dp) + ) + Text( + text = item.name, + color = Color.White, + textAlign = TextAlign.Center, + fontWeight = FontWeight.Bold, + fontSize = (15 * item.actionScale.value).sp + ) + } + } + // If this is a special action (except LOOP END), show a settings button to open popup + if (item::class.simpleName == "SpecialAction" && item.name != "LOOP END") { + IconButton( + onClick = { + println("Action pressed: " + item::class.simpleName) + togglePopUp = true + selectedActionIndex = index + }, modifier = Modifier - .size(70.dp) - ) - Text( - text = item.name, - color = Color.White, - textAlign = TextAlign.Center, - fontWeight = FontWeight.Bold, - ) + .align(Alignment.TopStart) + .padding(4.dp) + .size(30.dp) + ) { + Icon( + imageVector = Icons.Filled.Settings, + contentDescription = "Bold Close", + tint = Color.White, + modifier = Modifier.fillMaxSize() + ) + } } - } - // If this is a special action (except LOOP END), show a settings button to open popup - if(item::class.simpleName == "SpecialAction" && item.name != "LOOP END") - { + // Close (X) button to remove the action from the bar IconButton( onClick = { - println("Action pressed: " + item::class.simpleName) - togglePopUp = true - selectedActionIndex = index + removeActionFromSequenceBar(actionsInSequenceBar, index) + Log.d("SequenceBar", "Removed action: ${item.name}") + + // If actions are few, animate scroll back to the start + if (actionsInSequenceBar.size < 5) { + coroutineScope.launch { + scrollState.animateScrollTo( + 0, + animationSpec = tween( + durationMillis = 750, + easing = FastOutSlowInEasing + ) + ) + } + } }, modifier = Modifier - .align(Alignment.TopStart) + .align(Alignment.TopEnd) .padding(4.dp) .size(30.dp) ) { Icon( - imageVector = Icons.Filled.Settings, + imageVector = Icons.Filled.Close, contentDescription = "Bold Close", tint = Color.White, modifier = Modifier.fillMaxSize() ) } } - // Close (X) button to remove the action from the bar - IconButton( - onClick = { - removeActionFromSequenceBar(actionsInSequenceBar, index) - Log.d("SequenceBar", "Removed action: ${item.name}") - - // If actions are few, animate scroll back to the start - if(actionsInSequenceBar.size < 5) - { - coroutineScope.launch { - scrollState.animateScrollTo(0, animationSpec = tween(durationMillis = 750, easing = FastOutSlowInEasing)) - } - } - }, - modifier = Modifier - .align(Alignment.TopEnd) - .padding(4.dp) - .size(30.dp) - ) { - Icon( - imageVector = Icons.Filled.Close, - contentDescription = "Bold Close", - tint = Color.White, - modifier = Modifier.fillMaxSize() - ) - } } } }