Steps tree is now displayed alongside the visualizer

maxime.batista 1 year ago
parent a9d6e20eaf
commit 53d3254f7b

@ -67,7 +67,7 @@ class MainActivity : ComponentActivity() {
.addConverterFactory(EitherBodyConverter.create())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(EitherCallAdapterFactory.create())
.baseUrl("http://grospc:5254/")
.baseUrl("http://192.168.127.83:5254/")
.client(
OkHttpClient.Builder()
.addInterceptor { it.proceed(it.request()) }
@ -98,5 +98,5 @@ fun App(service: IQBallService) {
auth.getOrNull()!!.token
}
VisualizerPage(service, auth, 21)
VisualizerPage(service, auth, 3)
}

@ -3,14 +3,10 @@ package com.iqball.app.component
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -31,42 +27,51 @@ import com.iqball.app.model.tactic.ComponentId
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<ComponentId, Offset>,
val courtArea: MutableState<Rect>,
val zoomState: ZoomState
)
@Composable
fun BasketCourt(content: StepContent, type: CourtType) {
fun BasketCourt(
content: StepContent,
type: CourtType,
modifier: Modifier,
state: BasketCourtStates
) {
val courtImg = when (type) {
CourtType.Plain -> R.drawable.plain_court
CourtType.Half -> R.drawable.half_court
}
val zoomState = rememberZoomState()
val zoomState = state.zoomState
val components = content.components
val componentsOffset = remember { mutableStateMapOf<ComponentId, Offset>() }
var courtArea by remember { mutableStateOf(Rect.Zero) }
val componentsOffset = state.componentsOffsets
var courtArea by state.courtArea
val playersPixelsRadius = LocalDensity.current.run { PlayerPieceDiameterDp.dp.toPx() / 2 }
Box(
modifier = Modifier
.background(Color.LightGray)
.fillMaxSize(),
modifier = modifier,
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.width(IntrinsicSize.Min)
.zoomable(zoomState),
contentAlignment = Alignment.Center,
) {
Image(
painter = painterResource(id = courtImg),
contentDescription = "court",
modifier = Modifier
.background(Color.White)
.fillMaxSize()
)
Box(
@ -83,8 +88,11 @@ fun BasketCourt(content: StepContent, type: CourtType) {
drawContent()
}
) {
for (component in components) {
val modifier = Modifier
val componentModifier = Modifier
.onGloballyPositioned {
if (!componentsOffset.containsKey(component.id))
componentsOffset[component.id] = it.boundsInRoot().center
@ -94,9 +102,8 @@ fun BasketCourt(content: StepContent, type: CourtType) {
val info = getPlayerInfo(component, content)
PlayerPiece(
player = info,
modifier = modifier
modifier = componentModifier
.align(info.pos.toBiasAlignment())
)
}

@ -43,7 +43,6 @@ fun StepsTree(root: StepNodeInfo, selectedNodeId: Int, onNodeSelected: (StepNode
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.LightGray)
.freeScroll(scrollState)
.onGloballyPositioned {
if (globalOffset == Offset.Zero)

@ -5,32 +5,45 @@ import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.zIndex
import arrow.core.Either
import com.iqball.app.R
import com.iqball.app.component.BasketCourt
import com.iqball.app.component.BasketCourtStates
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.StepNodeInfo
import com.iqball.app.net.service.TacticService
import com.iqball.app.session.Token
import kotlinx.coroutines.runBlocking
import net.engawapg.lib.zoomable.rememberZoomState
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
@ -47,6 +60,7 @@ fun VisualizerPage(
tacticId: Int,
) {
val dataEither = remember { initializeVisualizer(service, auth, tacticId) }
val showTree = remember { mutableStateOf(true) }
val (info, rootStep) = when (dataEither) {
// On error return a text to print it to the user
@ -58,13 +72,12 @@ fun VisualizerPage(
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()
)
}
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
@ -72,21 +85,42 @@ fun VisualizerPage(
}
}
Log.d("CONTENT", content.toString())
Column {
VisualizerHeader(title = info.name)
VisualizerHeader(title = info.name, showTree)
when (screenOrientation) {
Configuration.ORIENTATION_PORTRAIT -> StepsTree(
root = rootStep,
Configuration.ORIENTATION_PORTRAIT -> StepsTree(root = rootStep,
selectedNodeId = selectedStepId,
onNodeSelected = { selectedStepId = it.id }
)
onNodeSelected = { selectedStepId = it.id })
Configuration.ORIENTATION_LANDSCAPE -> {
val courtOffsets =
remember(showTree.value, content) { mutableStateMapOf<ComponentId, Offset>() }
val courtArea = remember(showTree.value) { mutableStateOf(Rect.Zero) }
val courtZoomState = rememberZoomState()
remember(showTree.value, content) {
runBlocking {
courtZoomState.reset()
}
}
Configuration.ORIENTATION_LANDSCAPE -> BasketCourt(
content = content,
type = info.type
)
val courtModifier =
if (showTree.value) Modifier.width(IntrinsicSize.Min) else Modifier.fillMaxWidth()
Row(modifier = Modifier.background(Color.LightGray)) {
BasketCourt(
content = content,
type = info.type,
modifier = courtModifier,
state = BasketCourtStates(courtOffsets, courtArea, courtZoomState)
)
if (showTree.value) {
StepsTree(root = rootStep,
selectedNodeId = selectedStepId,
onNodeSelected = { selectedStepId = it.id })
}
}
}
else -> throw Exception("Could not determine device's orientation.")
}
@ -95,17 +129,18 @@ fun VisualizerPage(
}
@Composable
private fun VisualizerHeader(title: String) {
private fun VisualizerHeader(title: String, showTree: MutableState<Boolean>) {
Row(
modifier = Modifier
.fillMaxWidth()
.zIndex(10000F)
.background(Color.White),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
IconButton(
onClick = { /*TODO*/ }
) {
IconButton(onClick = { /*TODO*/ }) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Back",
@ -115,44 +150,39 @@ private fun VisualizerHeader(title: String) {
Text(text = title, color = Color.Black)
Text(text = "")
IconButton(onClick = { showTree.value = !showTree.value }) {
Icon(
painter = painterResource(id = R.drawable.tree_icon),
contentDescription = "toggle show tree"
)
}
}
}
private fun initializeVisualizer(
service: TacticService,
auth: Token,
tacticId: Int
service: TacticService, auth: Token, tacticId: Int
): Either<String, VisualizerInitialData> {
val (tacticInfo, tacticTree) = runBlocking {
val tacticInfo = service.getTacticInfo(auth, tacticId)
.map {
TacticInfo(
id = it.id,
name = it.name,
type = CourtType.valueOf(
it.courtType.lowercase().replaceFirstChar(Char::uppercaseChar)
),
creationDate = LocalDateTime.ofInstant(
Instant.ofEpochMilli(it.creationDate),
ZoneId.systemDefault()
)
val tacticInfo = service.getTacticInfo(auth, tacticId).map {
TacticInfo(
id = it.id, name = it.name, type = CourtType.valueOf(
it.courtType.lowercase().replaceFirstChar(Char::uppercaseChar)
), creationDate = LocalDateTime.ofInstant(
Instant.ofEpochMilli(it.creationDate), ZoneId.systemDefault()
)
}.onLeft {
Log.e(
"received error response from server when retrieving tacticInfo : {}",
it.toString()
)
}
)
}.onLeft {
Log.e(
"received error response from server when retrieving tacticInfo : {}", it.toString()
)
}
val tacticTree = service.getTacticStepsTree(auth, tacticId)
.map { it.root }
.onLeft {
Log.e(
"received error response from server when retrieving tactic steps tree: {}",
it.toString()
)
}
val tacticTree = service.getTacticStepsTree(auth, tacticId).map { it.root }.onLeft {
Log.e(
"received error response from server when retrieving tactic steps tree: {}",
it.toString()
)
}
Pair(tacticInfo.getOrNull(), tacticTree.getOrNull())
}

@ -1,5 +1,5 @@
[versions]
agp = "8.3.0"
agp = "8.2.2"
arrowCore = "1.2.1"
composeFreeScroll = "0.2.2"
converterGson = "2.9.0"

Loading…
Cancel
Save