From fc9a2181e93e5464967b27a268ab3624ad26d88f Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Thu, 21 Mar 2024 11:50:07 +0100 Subject: [PATCH] visualize parent's step with a grey color --- .../java/com/iqball/app/component/Actions.kt | 9 +- .../com/iqball/app/component/BasketCourt.kt | 114 +++++++++++------- .../com/iqball/app/component/PlayerPiece.kt | 4 +- .../iqball/app/model/tactic/StepNodeInfo.kt | 14 +++ .../com/iqball/app/page/VisualizerPage.kt | 59 +++++---- 5 files changed, 123 insertions(+), 77 deletions(-) diff --git a/app/src/main/java/com/iqball/app/component/Actions.kt b/app/src/main/java/com/iqball/app/component/Actions.kt index 6ae6025..6460dc0 100644 --- a/app/src/main/java/com/iqball/app/component/Actions.kt +++ b/app/src/main/java/com/iqball/app/component/Actions.kt @@ -26,9 +26,10 @@ private const val ArrowHeadHeightPx = 35F fun drawActions( drawer: ContentDrawScope, content: StepContent, - offsets: Map, + offsets: Map, area: Rect, - playersPixelsRadius: Float + playersPixelsRadius: Float, + color: Color ) { for (component in content.components) { val originPos = offsets[component.id]!! @@ -79,7 +80,7 @@ fun drawActions( path.lineTo(pathEnd.x, pathEnd.y) drawer.drawPath( path = path, - color = Color.Black, + color = color, style = strokeStyle ) } else { @@ -92,7 +93,7 @@ fun drawActions( type == ActionType.Dribble, playersPixelsRadius ), - color = Color.Black, + color = color, style = strokeStyle ) } diff --git a/app/src/main/java/com/iqball/app/component/BasketCourt.kt b/app/src/main/java/com/iqball/app/component/BasketCourt.kt index 8b5d87c..f6876cf 100644 --- a/app/src/main/java/com/iqball/app/component/BasketCourt.kt +++ b/app/src/main/java/com/iqball/app/component/BasketCourt.kt @@ -28,11 +28,11 @@ import com.iqball.app.model.tactic.CourtType import com.iqball.app.model.tactic.PlayerLike import com.iqball.app.model.tactic.StepContent import net.engawapg.lib.zoomable.ZoomState -import net.engawapg.lib.zoomable.rememberZoomState import net.engawapg.lib.zoomable.zoomable data class BasketCourtStates( - val componentsOffsets: MutableMap, + val stepComponentsOffsets: MutableMap, + val parentComponentsOffsets: MutableMap, val courtArea: MutableState, val zoomState: ZoomState ) @@ -40,6 +40,7 @@ data class BasketCourtStates( @Composable fun BasketCourt( content: StepContent, + parentContent: StepContent?, type: CourtType, modifier: Modifier, state: BasketCourtStates @@ -50,12 +51,6 @@ fun BasketCourt( } val zoomState = state.zoomState - val components = content.components - - val componentsOffset = state.componentsOffsets - var courtArea by state.courtArea - - val playersPixelsRadius = LocalDensity.current.run { PlayerPieceDiameterDp.dp.toPx() / 2 } Box( modifier = modifier, @@ -74,49 +69,76 @@ fun BasketCourt( .background(Color.White) ) - Box( - modifier = Modifier - .fillMaxSize() - .onGloballyPositioned { - if (courtArea == Rect.Zero) - courtArea = it.boundsInRoot() - } - .drawWithContent { - val relativeOffsets = - componentsOffset.mapValues { (it.value - courtArea.topLeft).toVector() } - drawActions(this, content, relativeOffsets, courtArea, playersPixelsRadius) - drawContent() - } - ) { + CourtContent( + courtAreaState = state.courtArea, + content = content, + offsets = state.stepComponentsOffsets, + isFromParent = false + ) + if (parentContent != null) { + CourtContent( + courtAreaState = state.courtArea, + content = parentContent, + offsets = state.parentComponentsOffsets, + isFromParent = true + ) + } + } + } +} +@Composable +private fun CourtContent( + courtAreaState: MutableState, + content: StepContent, + offsets: MutableMap, + isFromParent: Boolean +) { + var courtArea by courtAreaState + val playersPixelsRadius = LocalDensity.current.run { PlayerPieceDiameterDp.dp.toPx() / 2 } + + Box( + modifier = Modifier + .fillMaxSize() + .onGloballyPositioned { + if (courtArea == Rect.Zero) + courtArea = it.boundsInRoot() + } + .drawWithContent { + val relativeOffsets = + offsets.mapValues { (it.value - courtArea.topLeft).toVector() } + drawActions(this, content, relativeOffsets, courtArea, playersPixelsRadius, if (isFromParent) Color.Gray else Color.Black) + drawContent() + } + ) { - for (component in components) { - val componentModifier = Modifier - .onGloballyPositioned { - if (!componentsOffset.containsKey(component.id)) - componentsOffset[component.id] = it.boundsInRoot().center - } - when (component) { - is PlayerLike -> { - val info = getPlayerInfo(component, content) - PlayerPiece( - player = info, - modifier = componentModifier - .align(info.pos.toBiasAlignment()) - ) - } - is BallComponent -> BallPiece( - modifier = modifier - .align( - component.pos - .toPos() - .toBiasAlignment() - ) - ) - } + for (component in content.components) { + val componentModifier = Modifier + .onGloballyPositioned { + if (!offsets.containsKey(component.id)) + offsets[component.id] = it.boundsInRoot().center + } + when (component) { + is PlayerLike -> { + val info = getPlayerInfo(component, content) + PlayerPiece( + player = info, + isFromParent = isFromParent, + modifier = componentModifier + .align(info.pos.toBiasAlignment()) + ) } + + is BallComponent -> BallPiece( + modifier = componentModifier + .align( + component.pos + .toPos() + .toBiasAlignment() + ) + ) } } } diff --git a/app/src/main/java/com/iqball/app/component/PlayerPiece.kt b/app/src/main/java/com/iqball/app/component/PlayerPiece.kt index 0629b94..b679a3f 100644 --- a/app/src/main/java/com/iqball/app/component/PlayerPiece.kt +++ b/app/src/main/java/com/iqball/app/component/PlayerPiece.kt @@ -20,9 +20,9 @@ import com.iqball.app.ui.theme.Opponents const val PlayerPieceDiameterDp = 25 @Composable -fun PlayerPiece(player: PlayerInfo, modifier: Modifier = Modifier) { +fun PlayerPiece(player: PlayerInfo, modifier: Modifier = Modifier, isFromParent: Boolean) { - val color = if (player.team === PlayerTeam.Allies) Allies else Opponents + val color = if (isFromParent) Color.LightGray else if (player.team === PlayerTeam.Allies) Allies else Opponents return Surface( shape = CircleShape, diff --git a/app/src/main/java/com/iqball/app/model/tactic/StepNodeInfo.kt b/app/src/main/java/com/iqball/app/model/tactic/StepNodeInfo.kt index 3c3b7bf..f91931d 100644 --- a/app/src/main/java/com/iqball/app/model/tactic/StepNodeInfo.kt +++ b/app/src/main/java/com/iqball/app/model/tactic/StepNodeInfo.kt @@ -1,3 +1,17 @@ package com.iqball.app.model.tactic data class StepNodeInfo(val id: Int, val children: List) + + +fun getParent(root: StepNodeInfo, child: Int): StepNodeInfo? { + for (children in root.children) { + if (children.id == child) { + return root + } + val result = getParent(children, child) + if (result != null) + return result + } + + return null +} \ No newline at end of file diff --git a/app/src/main/java/com/iqball/app/page/VisualizerPage.kt b/app/src/main/java/com/iqball/app/page/VisualizerPage.kt index 6fe5499..2159878 100644 --- a/app/src/main/java/com/iqball/app/page/VisualizerPage.kt +++ b/app/src/main/java/com/iqball/app/page/VisualizerPage.kt @@ -39,10 +39,13 @@ import com.iqball.app.component.StepsTree import com.iqball.app.model.TacticInfo import com.iqball.app.model.tactic.ComponentId import com.iqball.app.model.tactic.CourtType +import com.iqball.app.model.tactic.StepContent import com.iqball.app.model.tactic.StepNodeInfo +import com.iqball.app.model.tactic.getParent import com.iqball.app.net.service.TacticService import com.iqball.app.session.Token import kotlinx.coroutines.runBlocking +import net.engawapg.lib.zoomable.ZoomState import net.engawapg.lib.zoomable.rememberZoomState import java.time.Instant import java.time.LocalDateTime @@ -62,47 +65,52 @@ fun VisualizerPage( val dataEither = remember { initializeVisualizer(service, auth, tacticId) } val showTree = remember { mutableStateOf(true) } - val (info, rootStep) = when (dataEither) { + val (info, stepsTree) = when (dataEither) { // On error return a text to print it to the user is Either.Left -> return Text(text = dataEither.value) is Either.Right -> dataEither.value } - val screenOrientation = LocalConfiguration.current.orientation - var selectedStepId by rememberSaveable { mutableIntStateOf(rootStep.id) } - val content = remember(selectedStepId) { - runBlocking { - val result = service.getTacticStepContent(auth, tacticId, selectedStepId).onLeft { - Log.e( - "received error response from server when retrieving root content: {}", - it.toString() - ) - } - when (result) { - is Either.Left -> throw Error("Unexpected error") - is Either.Right -> result.value - } + fun getStepContent(step: Int): StepContent = runBlocking { + val result = service.getTacticStepContent(auth, tacticId, step).onLeft { + Log.e( + "received error response from server when retrieving step content: {}", + it.toString() + ) + } + when (result) { + is Either.Left -> throw Error("Unexpected error") + is Either.Right -> result.value } } + val screenOrientation = LocalConfiguration.current.orientation + var selectedStepId by rememberSaveable { mutableIntStateOf(stepsTree.id) } + val (content, parentContent) = remember(selectedStepId) { + val parentId = getParent(stepsTree, selectedStepId)?.id + Pair( + getStepContent(selectedStepId), + if (parentId == null) null else getStepContent(parentId) + ) + } + + + Column { VisualizerHeader(title = info.name, showTree) when (screenOrientation) { - Configuration.ORIENTATION_PORTRAIT -> StepsTree(root = rootStep, + Configuration.ORIENTATION_PORTRAIT -> StepsTree(root = stepsTree, selectedNodeId = selectedStepId, onNodeSelected = { selectedStepId = it.id }) Configuration.ORIENTATION_LANDSCAPE -> { - val courtOffsets = + val stepOffsets = remember(showTree.value, content) { mutableStateMapOf() } + val parentOffsets = + remember(showTree.value, parentContent) { mutableStateMapOf() } val courtArea = remember(showTree.value) { mutableStateOf(Rect.Zero) } - val courtZoomState = rememberZoomState() - remember(showTree.value, content) { - runBlocking { - courtZoomState.reset() - } - } + val courtZoomState = remember(showTree.value, selectedStepId) { ZoomState() } val courtModifier = if (showTree.value) Modifier.width(IntrinsicSize.Min) else Modifier.fillMaxWidth() @@ -110,12 +118,13 @@ fun VisualizerPage( Row(modifier = Modifier.background(Color.LightGray)) { BasketCourt( content = content, + parentContent, type = info.type, modifier = courtModifier, - state = BasketCourtStates(courtOffsets, courtArea, courtZoomState) + state = BasketCourtStates(stepOffsets, parentOffsets, courtArea, courtZoomState) ) if (showTree.value) { - StepsTree(root = rootStep, + StepsTree(root = stepsTree, selectedNodeId = selectedStepId, onNodeSelected = { selectedStepId = it.id }) }