Skip to content
Snippets Groups Projects
Commit 9c20d22b authored by Dennis Abrikossov's avatar Dennis Abrikossov
Browse files

Merge branch '10-feature-larare-visa-instruktion' into 'Development'

Resolve "FEATURE:Lärare-visa-instruktion"

Closes #10

See merge request !15
parents 0bae2179 084aaeb4
Branches
No related tags found
4 merge requests!18Merging dev into main,!17Merge Dev into main,!16Sprint 1 done, mergin to main,!15Resolve "FEATURE:Lärare-visa-instruktion"
......@@ -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
......@@ -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)
)
}
}
/**
......
......@@ -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()
)
}
}
}
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment