parent
509909e13d
commit
5dd97d182a
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 6.0 KiB |
@ -0,0 +1,22 @@
|
||||
package com.iqball.app.component
|
||||
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.iqball.app.R
|
||||
import com.iqball.app.ui.theme.BallColor
|
||||
|
||||
const val BallPieceDiameterDp = 20
|
||||
|
||||
@Composable
|
||||
fun BallPiece(modifier: Modifier = Modifier) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ball),
|
||||
contentDescription = "ball",
|
||||
tint = BallColor,
|
||||
modifier = modifier.size(BallPieceDiameterDp.dp)
|
||||
)
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package com.iqball.app.component
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import com.iqball.app.R
|
||||
import com.iqball.app.domains.getPlayerInfo
|
||||
import com.iqball.app.model.tactic.BallComponent
|
||||
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.rememberZoomState
|
||||
import net.engawapg.lib.zoomable.zoomable
|
||||
|
||||
@Composable
|
||||
fun BasketCourt(content: StepContent, type: CourtType) {
|
||||
val courtImg = when (type) {
|
||||
CourtType.Plain -> R.drawable.plain_court
|
||||
CourtType.Half -> R.drawable.half_court
|
||||
}
|
||||
|
||||
val zoomState = rememberZoomState()
|
||||
val components = content.components
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.background(Color.LightGray)
|
||||
.fillMaxSize()
|
||||
.zoomable(zoomState),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box {
|
||||
Image(
|
||||
painter = painterResource(id = courtImg),
|
||||
contentDescription = "court",
|
||||
modifier = Modifier
|
||||
.background(Color.White)
|
||||
.align(Alignment.Center)
|
||||
.fillMaxHeight()
|
||||
)
|
||||
|
||||
for (component in components) {
|
||||
|
||||
when (component) {
|
||||
is PlayerLike -> {
|
||||
val info = getPlayerInfo(component, content)
|
||||
PlayerPiece(
|
||||
player = info,
|
||||
modifier = Modifier.align(info.pos.toBiasAlignment())
|
||||
)
|
||||
}
|
||||
|
||||
is BallComponent -> BallPiece(
|
||||
modifier = Modifier.align(component.pos.toPos().toBiasAlignment())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package com.iqball.app.component
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.iqball.app.model.tactic.PlayerInfo
|
||||
import com.iqball.app.model.tactic.PlayerTeam
|
||||
import com.iqball.app.ui.theme.Allies
|
||||
import com.iqball.app.ui.theme.Opponents
|
||||
|
||||
const val PlayerPieceDiameterDp = 25
|
||||
|
||||
@Composable
|
||||
fun PlayerPiece(player: PlayerInfo, modifier: Modifier = Modifier) {
|
||||
|
||||
val color = if (player.team === PlayerTeam.Allies) Allies else Opponents
|
||||
|
||||
return Surface(
|
||||
shape = CircleShape,
|
||||
border = if (player.ballState.hasBall()) BorderStroke(2.dp, Color.Black) else null,
|
||||
modifier = modifier
|
||||
.alpha(if (player.isPhantom) .5F else 1F)
|
||||
) {
|
||||
Text(
|
||||
text = player.role,
|
||||
textAlign = TextAlign.Center,
|
||||
color = Color.Black,
|
||||
modifier = Modifier
|
||||
.background(color)
|
||||
.size(PlayerPieceDiameterDp.dp)
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
package com.iqball.app.domains
|
||||
|
||||
import arrow.core.merge
|
||||
import com.iqball.app.geo.Vector
|
||||
import com.iqball.app.model.tactic.BallComponent
|
||||
import com.iqball.app.model.tactic.FixedPosition
|
||||
import com.iqball.app.model.tactic.MalformedStepContentException
|
||||
import com.iqball.app.model.tactic.PhantomComponent
|
||||
import com.iqball.app.model.tactic.PlayerComponent
|
||||
import com.iqball.app.model.tactic.PlayerInfo
|
||||
import com.iqball.app.model.tactic.PlayerLike
|
||||
import com.iqball.app.model.tactic.RelativePositioning
|
||||
import com.iqball.app.model.tactic.StepComponent
|
||||
import com.iqball.app.model.tactic.StepContent
|
||||
|
||||
/**
|
||||
* Converts the phantom's [PhantomPositioning] to a XY Position
|
||||
* if the phantom is a [RelativePositioning], the XY coords are determined
|
||||
* using the attached component, and by expecting that there is an action on the attached component that
|
||||
* targets the given phantom.
|
||||
* If so, then the position is determined by projecting the attached component's position, and the direction
|
||||
* of the action's last segment.
|
||||
* @throws MalformedStepContentException if the step content contains incoherent data
|
||||
*/
|
||||
fun computePhantomPosition(phantom: PhantomComponent, content: StepContent): Vector {
|
||||
val pos = phantom.pos
|
||||
if (pos is FixedPosition)
|
||||
return pos.toPos()
|
||||
|
||||
pos as RelativePositioning
|
||||
|
||||
val phantomBefore = getPlayerBefore(phantom, content)!!
|
||||
|
||||
val referentId = pos.attach
|
||||
val actions = phantomBefore.actions
|
||||
val linkAction = actions.find { it.target.isLeft(referentId::equals) }
|
||||
?: throw MalformedStepContentException("phantom ${phantom.id} is casted by ${phantom}, but there is no action between them.")
|
||||
|
||||
val segments = linkAction.segments
|
||||
val lastSegment = segments.last()
|
||||
|
||||
val referent = content.findComponent<StepComponent>(referentId)!!
|
||||
val referentPos = computeComponentPosition(referent, content)
|
||||
val directionalPos = lastSegment.controlPoint
|
||||
?: segments.elementAtOrNull(segments.size - 2)
|
||||
?.next
|
||||
?.mapLeft { computeComponentPosition(content.findComponent(it)!!, content) }
|
||||
?.merge()
|
||||
?: computeComponentPosition(phantomBefore, content)
|
||||
|
||||
val axisSegment = (referentPos - directionalPos)
|
||||
val segmentLength = axisSegment.norm()
|
||||
val projectedVector = Vector(
|
||||
x = (axisSegment.x / segmentLength) * 0.05,
|
||||
y = (axisSegment.y / segmentLength) * 0.05,
|
||||
)
|
||||
|
||||
return referentPos + projectedVector
|
||||
}
|
||||
|
||||
fun computeComponentPosition(component: StepComponent, content: StepContent): Vector =
|
||||
when (component) {
|
||||
is PhantomComponent -> computePhantomPosition(component, content)
|
||||
is PlayerComponent -> component.pos.toPos()
|
||||
is BallComponent -> component.pos.toPos()
|
||||
}
|
||||
|
||||
|
||||
fun getPlayerBefore(phantom: PhantomComponent, content: StepContent): PlayerLike? {
|
||||
val origin = content.findComponent<PlayerComponent>(phantom.originPlayerId)!!
|
||||
val items = origin.path!!.items
|
||||
val phantomIdx = items.indexOf(phantom.id)
|
||||
if (phantomIdx == -1)
|
||||
throw MalformedStepContentException("phantom player is not registered it its origin's path")
|
||||
if (phantomIdx == 0)
|
||||
return origin
|
||||
return content.findComponent<PhantomComponent>(items[phantomIdx - 1])
|
||||
}
|
||||
|
||||
fun getPlayerInfo(player: PlayerLike, content: StepContent): PlayerInfo {
|
||||
|
||||
return when (player) {
|
||||
is PlayerComponent -> PlayerInfo(
|
||||
player.team,
|
||||
player.role,
|
||||
false,
|
||||
player.pos.toPos(),
|
||||
player.id,
|
||||
player.actions,
|
||||
player.ballState
|
||||
)
|
||||
|
||||
is PhantomComponent -> {
|
||||
val origin = content.findComponent<PlayerComponent>(player.originPlayerId)!!
|
||||
val pos = computePhantomPosition(player, content)
|
||||
|
||||
PlayerInfo(
|
||||
origin.team,
|
||||
origin.role,
|
||||
true,
|
||||
pos,
|
||||
player.id,
|
||||
player.actions,
|
||||
player.ballState
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
package com.iqball.app.geo
|
||||
|
||||
data class Area(val pos: Vector, val width: Float, val height: Float)
|
@ -0,0 +1,25 @@
|
||||
package com.iqball.app.geo
|
||||
|
||||
import androidx.compose.ui.BiasAlignment
|
||||
import kotlin.math.sqrt
|
||||
|
||||
typealias Pos = Vector
|
||||
|
||||
data class Vector(val x: Double, val y: Double) {
|
||||
fun toBiasAlignment(): BiasAlignment =
|
||||
BiasAlignment((x * 2 - 1).toFloat(), (y * 2 - 1).toFloat())
|
||||
|
||||
infix operator fun minus(other: Vector) = Vector(x - other.x, y - other.y)
|
||||
infix operator fun plus(other: Vector) = Vector(x + other.x, y + other.y)
|
||||
|
||||
fun distanceWith(other: Vector) =
|
||||
sqrt(((x - other.x) * (x - other.x) + (y - other.y) * (y - other.y)))
|
||||
|
||||
fun norm() = NullVector.distanceWith(this)
|
||||
|
||||
fun posWithinArea(area: Area) = Vector(x * area.width, y * area.height)
|
||||
fun ratioWithinArea(area: Area) =
|
||||
Vector((x - area.pos.x) * area.width, (y - area.pos.y) * area.height)
|
||||
}
|
||||
|
||||
val NullVector = Vector(.0, .0)
|
@ -0,0 +1,11 @@
|
||||
package com.iqball.app.model
|
||||
|
||||
import com.iqball.app.model.tactic.CourtType
|
||||
import java.time.LocalDateTime
|
||||
|
||||
data class TacticInfo(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val type: CourtType,
|
||||
val creationDate: LocalDateTime
|
||||
)
|
@ -0,0 +1,23 @@
|
||||
package com.iqball.app.model.tactic
|
||||
|
||||
import arrow.core.Either
|
||||
import com.iqball.app.geo.Vector
|
||||
|
||||
|
||||
enum class ActionType {
|
||||
Screen,
|
||||
Dribble,
|
||||
Move,
|
||||
Shoot
|
||||
}
|
||||
|
||||
data class Segment(
|
||||
val next: Either<ComponentId, Vector>,
|
||||
val controlPoint: Vector?
|
||||
)
|
||||
|
||||
data class Action(
|
||||
val type: ActionType,
|
||||
val target: Either<ComponentId, Vector>,
|
||||
val segments: List<Segment>
|
||||
)
|
@ -0,0 +1,13 @@
|
||||
package com.iqball.app.model.tactic
|
||||
|
||||
enum class BallState {
|
||||
None,
|
||||
HoldsOrigin,
|
||||
HoldsByPass,
|
||||
Passed,
|
||||
PassedOrigin;
|
||||
|
||||
|
||||
fun hasBall() = this != None
|
||||
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package com.iqball.app.model.tactic
|
||||
|
||||
enum class CourtType {
|
||||
Plain,
|
||||
Half
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
package com.iqball.app.model.tactic
|
||||
|
||||
data class MovementPath(val items: List<ComponentId>)
|
@ -0,0 +1,13 @@
|
||||
package com.iqball.app.model.tactic
|
||||
|
||||
import com.iqball.app.geo.Vector
|
||||
|
||||
data class PlayerInfo(
|
||||
val team: PlayerTeam,
|
||||
val role: String,
|
||||
val isPhantom: Boolean,
|
||||
val pos: Vector,
|
||||
val id: ComponentId,
|
||||
val actions: List<Action>,
|
||||
val ballState: BallState
|
||||
)
|
@ -0,0 +1,6 @@
|
||||
package com.iqball.app.model.tactic
|
||||
|
||||
enum class PlayerTeam {
|
||||
Allies,
|
||||
Opponents
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.iqball.app.model.tactic
|
||||
|
||||
import com.iqball.app.geo.Pos
|
||||
|
||||
sealed interface Positioning
|
||||
data class RelativePositioning(val attach: ComponentId) : Positioning
|
||||
data class FixedPosition(val x: Double, val y: Double) : Positioning {
|
||||
fun toPos() = Pos(x, y)
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package com.iqball.app.model.tactic
|
||||
|
||||
typealias ComponentId = String
|
||||
|
||||
|
||||
sealed interface StepComponent {
|
||||
val id: ComponentId
|
||||
val actions: List<Action>
|
||||
}
|
||||
|
||||
sealed interface PositionableComponent<P : Positioning> {
|
||||
val pos: P
|
||||
}
|
||||
|
||||
sealed interface PlayerLike : PositionableComponent<Positioning>, StepComponent {
|
||||
val ballState: BallState
|
||||
}
|
||||
|
||||
data class PlayerComponent(
|
||||
val path: MovementPath?,
|
||||
val team: PlayerTeam,
|
||||
val role: String,
|
||||
override val ballState: BallState,
|
||||
override val pos: FixedPosition,
|
||||
override val id: ComponentId,
|
||||
override val actions: List<Action>,
|
||||
) : PlayerLike, StepComponent
|
||||
|
||||
data class PhantomComponent(
|
||||
val attachedTo: ComponentId?,
|
||||
val originPlayerId: ComponentId,
|
||||
override val ballState: BallState,
|
||||
override val pos: Positioning,
|
||||
override val id: ComponentId,
|
||||
override val actions: List<Action>
|
||||
) : PlayerLike, StepComponent
|
||||
|
||||
data class BallComponent(
|
||||
override val id: ComponentId,
|
||||
override val actions: List<Action>,
|
||||
override val pos: FixedPosition
|
||||
) : StepComponent, PositionableComponent<FixedPosition>
|
@ -0,0 +1,15 @@
|
||||
package com.iqball.app.model.tactic
|
||||
|
||||
import java.lang.RuntimeException
|
||||
|
||||
data class StepContent(val components: List<StepComponent>) {
|
||||
inline fun <reified C: StepComponent> findComponent(id: String): C? {
|
||||
val value = components.find { it.id == id } ?: return null
|
||||
if (!C::class.java.isAssignableFrom(value.javaClass))
|
||||
return null
|
||||
return value as C
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MalformedStepContentException(msg: String, cause: Throwable? = null): RuntimeException(msg, cause)
|
@ -0,0 +1,3 @@
|
||||
package com.iqball.app.model.tactic
|
||||
|
||||
data class StepNodeInfo(val id: Int, val children: List<StepNodeInfo>)
|
@ -1,4 +1,4 @@
|
||||
package com.iqball.app.api
|
||||
package com.iqball.app.net
|
||||
|
||||
import arrow.core.Either
|
||||
import okhttp3.ResponseBody
|
@ -1,11 +1,7 @@
|
||||
package com.iqball.app.api.service
|
||||
package com.iqball.app.net.service
|
||||
|
||||
import arrow.core.Either
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.serialization.Serializable
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface AuthService {
|
@ -1,9 +1,8 @@
|
||||
package com.iqball.app.api.service
|
||||
package com.iqball.app.net.service
|
||||
|
||||
import arrow.core.Either
|
||||
import retrofit2.Call
|
||||
|
||||
typealias ErrorResponseResult = Map<String, Array<String>>
|
||||
typealias APIResult<R> = Either<ErrorResponseResult, R>
|
||||
|
||||
interface IQBallService : AuthService, UserService
|
||||
interface IQBallService : AuthService, UserService, TacticService
|
@ -0,0 +1,44 @@
|
||||
package com.iqball.app.net.service
|
||||
|
||||
import com.iqball.app.model.tactic.StepContent
|
||||
import com.iqball.app.model.tactic.StepNodeInfo
|
||||
import com.iqball.app.session.Token
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.Path
|
||||
|
||||
interface TacticService {
|
||||
|
||||
data class GetTacticInfoResponse(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val courtType: String,
|
||||
val creationDate: Long
|
||||
)
|
||||
|
||||
@GET("tactics/{tacticId}")
|
||||
suspend fun getTacticInfo(
|
||||
@Header("Authorization") auth: Token,
|
||||
@Path("tacticId") tacticId: Int
|
||||
): APIResult<GetTacticInfoResponse>
|
||||
|
||||
data class GetTacticStepsTreeResponse(
|
||||
val root: StepNodeInfo
|
||||
)
|
||||
|
||||
@GET("tactics/{tacticId}/tree")
|
||||
suspend fun getTacticStepsTree(
|
||||
@Header("Authorization") auth: Token,
|
||||
@Path("tacticId") tacticId: Int
|
||||
): APIResult<GetTacticStepsTreeResponse>
|
||||
|
||||
|
||||
|
||||
@GET("tactics/{tacticId}/steps/{stepId}")
|
||||
suspend fun getTacticStepContent(
|
||||
@Header("Authorization") auth: Token,
|
||||
@Path("tacticId") tacticId: Int,
|
||||
@Path("stepId") stepId: Int
|
||||
): APIResult<StepContent>
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.iqball.app.api.service
|
||||
package com.iqball.app.net.service
|
||||
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
@ -0,0 +1,158 @@
|
||||
package com.iqball.app.page
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.res.Configuration
|
||||
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.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
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.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import arrow.core.Either
|
||||
import arrow.core.flatMap
|
||||
import com.iqball.app.component.BasketCourt
|
||||
import com.iqball.app.model.TacticInfo
|
||||
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.net.service.TacticService
|
||||
import com.iqball.app.session.Token
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
|
||||
private data class VisualizerInitialData(
|
||||
val info: TacticInfo,
|
||||
val rootStep: StepNodeInfo,
|
||||
val rootContent: StepContent
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun VisualizerPage(
|
||||
service: TacticService,
|
||||
auth: Token,
|
||||
tacticId: Int,
|
||||
) {
|
||||
val dataEither = initializeVisualizer(service, auth, tacticId)
|
||||
|
||||
val (info, rootStep, rootContent) = when (dataEither) {
|
||||
is Either.Left -> return Text(text = dataEither.value)
|
||||
is Either.Right -> dataEither.value
|
||||
}
|
||||
|
||||
val screenOrientation = LocalConfiguration.current.orientation
|
||||
|
||||
Column {
|
||||
VisualizerHeader(title = info.name)
|
||||
when (screenOrientation) {
|
||||
Configuration.ORIENTATION_PORTRAIT -> Text(
|
||||
text = "Visualizing Tactic Steps tree",
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Green)
|
||||
)
|
||||
|
||||
Configuration.ORIENTATION_LANDSCAPE -> BasketCourt(
|
||||
content = rootContent,
|
||||
type = info.type
|
||||
)
|
||||
|
||||
else -> throw Exception("Could not determine device's orientation.")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun VisualizerHeader(title: String) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(Color.White),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { /*TODO*/ }
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
tint = Color.Black
|
||||
)
|
||||
}
|
||||
|
||||
Text(text = title, color = Color.Black)
|
||||
|
||||
Text(text = "")
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeVisualizer(
|
||||
service: TacticService,
|
||||
auth: Token,
|
||||
tacticId: Int
|
||||
): Either<String, VisualizerInitialData> {
|
||||
val (tacticInfo, tacticTree, rootStepContent) = 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()
|
||||
)
|
||||
)
|
||||
}.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 rootStepContent = tacticTree
|
||||
.flatMap {
|
||||
service.getTacticStepContent(auth, tacticId, it.id)
|
||||
.onLeft {
|
||||
Log.e(
|
||||
"received error response from server when retrieving root content: {}",
|
||||
it.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Triple(tacticInfo.getOrNull(), tacticTree.getOrNull(), rootStepContent.getOrNull())
|
||||
}
|
||||
|
||||
if (tacticInfo == null || tacticTree == null || rootStepContent == null) {
|
||||
return Either.Left("Unable to retrieve tactic information")
|
||||
}
|
||||
|
||||
|
||||
return Either.Right(VisualizerInitialData(tacticInfo, tacticTree, rootStepContent))
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package com.iqball.app.serialization
|
||||
|
||||
import arrow.core.Either
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.JsonReader
|
||||
import com.squareup.moshi.JsonWriter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.rawType
|
||||
import java.lang.ClassCastException
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.lang.reflect.Type
|
||||
|
||||
|
||||
private class EitherTypeAdapter(val leftType: JsonPrimitiveType, val rightType: JsonPrimitiveType) :
|
||||
JsonAdapter<Either<*, *>>() {
|
||||
override fun fromJson(reader: JsonReader): Either<*, *>? {
|
||||
val value = reader.readJsonValue() ?: return null
|
||||
|
||||
val valueJsonType = value.javaClass.getJsonPrimitive()
|
||||
if (valueJsonType == leftType)
|
||||
return Either.Left(value)
|
||||
|
||||
if (valueJsonType == rightType)
|
||||
return Either.Right(value)
|
||||
throw ClassCastException("Cannot cast a value of type " + value.javaClass + " as either " + leftType.name.lowercase() + " or " + rightType.name.lowercase())
|
||||
}
|
||||
|
||||
|
||||
override fun toJson(writer: JsonWriter, value: Either<*, *>?) {
|
||||
when (value) {
|
||||
is Either.Left -> writer.jsonValue(value.value)
|
||||
is Either.Right -> writer.jsonValue(value.value)
|
||||
null -> writer.nullValue()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
object EitherTypeAdapterFactory: JsonAdapter.Factory {
|
||||
override fun create(
|
||||
type: Type,
|
||||
annotations: MutableSet<out Annotation>,
|
||||
moshi: Moshi
|
||||
): JsonAdapter<*>? {
|
||||
if (type !is ParameterizedType)
|
||||
return null
|
||||
if (type.rawType != Either::class.java)
|
||||
return null
|
||||
val leftType = type.actualTypeArguments[0].rawType.getJsonPrimitive()
|
||||
val rightType = type.actualTypeArguments[1].rawType.getJsonPrimitive()
|
||||
if (leftType == rightType) {
|
||||
throw UnsupportedOperationException("Cannot handle Either types with both sides being object, array, string or number. Provided type is : $type")
|
||||
}
|
||||
return EitherTypeAdapter(leftType, rightType)
|
||||
}
|
||||
}
|
||||
|
||||
private enum class JsonPrimitiveType {
|
||||
Array,
|
||||
Object,
|
||||
String,
|
||||
Number;
|
||||
}
|
||||
|
||||
private fun Class<*>.getJsonPrimitive(): JsonPrimitiveType {
|
||||
if (isPrimitive)
|
||||
return JsonPrimitiveType.Number
|
||||
if (isArray)
|
||||
return JsonPrimitiveType.Array
|
||||
if (this == String::class.java)
|
||||
return JsonPrimitiveType.String
|
||||
return JsonPrimitiveType.Object
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package com.iqball.app.serialization
|
||||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.JsonReader
|
||||
import com.squareup.moshi.JsonWriter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.rawType
|
||||
import java.lang.reflect.Type
|
||||
import java.util.NoSuchElementException
|
||||
|
||||
class EnumTypeAdapter<E : Enum<E>>(
|
||||
values: Map<String, E>,
|
||||
private val bindNames: Boolean,
|
||||
private val ignoreCase: Boolean,
|
||||
private val fallback: E?,
|
||||
private val clazz: Class<E>
|
||||
) : JsonAdapter<E>() {
|
||||
|
||||
private val values = if (ignoreCase) values.mapKeys { it.key.lowercase() } else values
|
||||
|
||||
override fun fromJson(reader: JsonReader): E {
|
||||
val value = reader.nextString()
|
||||
val key = if (ignoreCase) value.lowercase() else value
|
||||
|
||||
var result = values[key]
|
||||
if (result == null && bindNames) {
|
||||
result = clazz.enumConstants?.find { it.name.lowercase() == key }
|
||||
}
|
||||
|
||||
return result
|
||||
?: fallback
|
||||
?: throw NoSuchElementException("No enum variant matched given values bindings, and no fallback was provided (value = $value, enum type = $clazz)")
|
||||
}
|
||||
|
||||
override fun toJson(writer: JsonWriter, value: E?) {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class EnumTypeAdapterFactory<E : Enum<E>>(
|
||||
private val values: Map<String, E>,
|
||||
private val bindNames: Boolean,
|
||||
private val ignoreCase: Boolean,
|
||||
private val fallback: E?,
|
||||
private val clazz: Class<E>
|
||||
) : JsonAdapter.Factory {
|
||||
override fun create(
|
||||
type: Type,
|
||||
annotations: MutableSet<out Annotation>,
|
||||
moshi: Moshi
|
||||
): JsonAdapter<*>? {
|
||||
if (type.rawType != clazz)
|
||||
return null
|
||||
|
||||
return EnumTypeAdapter(values, bindNames, ignoreCase, fallback, clazz)
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
|
||||
class Builder<E : Enum<E>>(val values: MutableMap<String, E> = HashMap()) {
|
||||
infix fun String.means(e: E) {
|
||||
values[this] = e
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified E : Enum<E>> create(
|
||||
ignoreCase: Boolean = false,
|
||||
bindNames: Boolean = true,
|
||||
fallback: E? = null,
|
||||
build: Builder<E>.() -> Unit = {}
|
||||
): EnumTypeAdapterFactory<E> {
|
||||
val builder = Builder<E>()
|
||||
build(builder)
|
||||
return EnumTypeAdapterFactory(builder.values, bindNames, ignoreCase, fallback, E::class.java)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
package com.iqball.app.session
|
||||
typealias Token = String
|
||||
|
||||
data class Authentication(val token: String, val expirationDate: Long)
|
||||
|
@ -0,0 +1,38 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="1280dp"
|
||||
android:height="1276dp"
|
||||
android:viewportWidth="1280"
|
||||
android:viewportHeight="1276">
|
||||
<path
|
||||
android:pathData="M608.5,0.6c-112.4,6.6 -212.4,37.8 -305.5,95.2 -11.3,7 -15,9.8 -11.5,8.8 5,-1.4 35,-3.7 55.5,-4.3 92.5,-2.4 175.5,19.8 257.3,69.1 9.5,5.6 32.1,20.2 50.2,32.2 51.1,34 60,37.3 75.3,28 28.1,-17 48.1,-45.7 55.9,-80 2.4,-10.5 2.4,-39.3 -0.1,-50.6 -5.9,-27.7 -20.2,-55.7 -41.7,-81.3 -4.5,-5.4 -9.1,-10.2 -10.3,-10.6 -1.9,-0.8 -16,-2.5 -41.1,-5.1 -9.9,-1.1 -72.4,-2.1 -84,-1.4z"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="M763,12.5c0,0.2 2.8,4.8 6.3,10.2 21,32.4 33.5,62.6 37.8,91.3 1.6,11.2 1.4,31 -0.6,42.4 -5.6,32.8 -24.5,65.1 -59.1,101.4 -7.9,8.3 -10.5,11.7 -9.8,12.7 1.1,1.7 95.3,89.5 95.9,89.5 0.3,-0 13.6,-10.5 29.7,-23.3 108.5,-86.3 148.5,-114 206.5,-143.1 9.4,-4.7 17.3,-9.1 17.5,-9.8 0.7,-2 -23.2,-24 -46.7,-42.9 -76.6,-61.6 -165.4,-104.3 -262.2,-125.9 -13.5,-3.1 -15.3,-3.3 -15.3,-2.5z"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="M305.5,114.5c-12.7,1.3 -26.2,3.6 -35.9,6.1 -6.9,1.8 -15.7,8.1 -39.7,28 -98.4,82.3 -169.5,190.9 -204.9,313.5 -8.4,29.1 -14,55 -18.5,85.4 -1.9,13 -2.9,23.1 -2.3,22.5 0.3,-0.3 1.6,-3.4 2.8,-7 3.6,-10.3 12.4,-26.7 19.5,-36.3 38.6,-51.8 115.1,-90.1 247.5,-123.8 29.1,-7.4 55.3,-13.4 91,-20.9 10.7,-2.3 23.6,-5 28.5,-6 5,-1.1 15.8,-3.3 24,-5 8.3,-1.7 19.1,-3.9 24,-5 5,-1 13.3,-2.8 18.5,-3.9 107.9,-22.8 159.9,-39.2 191.5,-60.4 9.6,-6.4 20.5,-18.1 24.4,-26.2 2.8,-5.7 3.5,-8.2 3.5,-13.5 0.1,-6.2 -0.2,-7 -4.4,-13.5 -14.2,-21.8 -52.6,-49.5 -101.9,-73.7 -92,-44.9 -194.1,-68 -267.6,-60.3z"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="M1094,208.6c-6.3,4.1 -17.6,11.4 -25,16.2 -29.9,19.3 -79.8,54.7 -121.5,86.1 -42.3,31.8 -89.9,71.1 -89.3,73.6 0.2,0.6 18.9,24.6 41.7,53.2l41.4,52 17.1,-8.2c52.8,-25.5 97.5,-37.5 139.8,-37.5 57.4,-0 106,23.9 146.4,72 8.5,10.1 21.9,28.6 26.4,36.5 1.8,3.1 3.4,5.4 3.6,5.2 1.1,-1.1 -6.9,-45.6 -12,-67.1 -23.5,-97.6 -69.2,-187.5 -134.7,-264.6 -5.4,-6.3 -12.6,-14.5 -16.1,-18.2l-6.3,-6.6 -11.5,7.4z"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="M712.5,292.7c-7,6.2 -23.1,18.1 -32.7,24.2 -49.1,30.8 -112,53.1 -210.6,74.6 -9,2 -45.2,9.4 -80.4,16.5 -112.8,22.8 -149.1,31.7 -197.8,48.5 -93.6,32.3 -152.7,76.1 -186,137.8l-4.2,7.8 -0.5,25.2c-3.1,150.3 47.2,297.4 141.5,413.7 4.7,5.8 8.7,10.7 8.8,10.8 0.8,1.1 5,-3.5 21,-23.3 106.7,-131.8 204.2,-240.8 317.4,-354.9 95.3,-96 190.8,-184.6 293.3,-272.1 13.5,-11.6 24.6,-21.5 24.7,-22 0,-1 -89,-90.5 -90,-90.5 -0.3,0.1 -2.3,1.7 -4.5,3.7z"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="M825.5,414.2c-88.6,66.8 -153.4,121.6 -237,200.4 -102.8,96.9 -212.5,213.6 -329.9,350.9 -13.2,15.4 -26.5,30.9 -29.6,34.5 -14.2,16.4 -59.2,70.3 -59.6,71.5 -0.7,1.6 30,32.6 46.6,47.1 67.8,59.3 142.5,102 226,129.4 23.1,7.5 49,14.3 73,19 20.7,4 41.3,7.2 56,8.5 6.2,0.6 6.9,0.4 15.5,-3.8 19.2,-9.4 34.6,-20.9 51.5,-38.3 35.1,-36.3 60.1,-85.4 83.9,-164.9 5.8,-19.2 15.8,-58.7 21.1,-83.5 4,-18.5 11.4,-54.1 19,-92 9.8,-48.6 16.8,-82.5 20,-96.5 0.5,-2.2 1.6,-6.9 2.4,-10.5 0.8,-3.6 3.6,-15.1 6.1,-25.5 4,-16.4 5.9,-23.5 11.5,-43.5 5.2,-18.7 16.9,-53.2 24.2,-71.5 23.2,-58.2 56.2,-106.2 91.1,-132.5 2.5,-1.9 4.5,-3.8 4.4,-4.2 -0.2,-0.8 -83.8,-103.9 -84,-103.7 -0.1,-0 -5.6,4.1 -12.2,9.1z"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="M1057,466.7c-35.8,5.4 -57.9,14.9 -74.1,31.9 -8.4,8.9 -9.3,10.6 -8.6,16.8 1,10 6.5,19.9 39.7,72.1 36.8,57.8 50.8,81.8 69.1,118.5 37.9,76 56.7,141.3 61.1,213.2 2.4,39.2 -3,86.3 -14.2,124.3 -1.6,5.4 -2.8,10 -2.7,10.2 0.8,0.8 29.7,-36.9 41.9,-54.7 72.2,-105.5 110.9,-231.3 110.8,-360 0,-29.4 -1.5,-49 -4,-54.7 -3,-6.8 -12.7,-22.8 -19.4,-31.8 -24,-32.6 -59,-58.1 -99.9,-72.9 -27.1,-9.8 -49.8,-13.7 -79.1,-13.5 -10,0.1 -19.2,0.3 -20.6,0.6z"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="M926.5,544.3c-29.2,9.7 -54.1,40.3 -74.4,91.4 -17.4,43.9 -31.2,96.4 -52.1,197.5 -1.6,8.2 -4.4,21.4 -6,29.5 -1.7,8.2 -3.9,18.8 -5,23.8 -1.1,4.9 -3.3,15.7 -5,24 -38.9,189.3 -79.8,291.5 -139,347.8 -5.7,5.4 -12.3,11.2 -14.7,13l-4.5,3.2 20.3,0.3c56.8,0.9 124.7,-9.5 184.4,-28.3 92.8,-29.2 174.1,-76.5 245.1,-142.3 17.9,-16.6 21.6,-20.7 25.3,-28.1 9.6,-19 16.3,-41.3 20.2,-67.6 2,-12.8 2.3,-19.2 2.3,-43 0,-34.4 -2.2,-56 -9.5,-92.7 -19.1,-95.9 -69.6,-210.9 -124.9,-284.3 -17.5,-23.1 -35.1,-41 -42.8,-43.3 -6.4,-1.9 -15.3,-2.3 -19.7,-0.9z"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#00000000"/>
|
||||
</vector>
|
@ -0,0 +1,171 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="269dp"
|
||||
android:height="309dp"
|
||||
android:viewportWidth="269"
|
||||
android:viewportHeight="309">
|
||||
<path
|
||||
android:pathData="M24,236L24,26"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M248,25L248,236"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M249,237L23,237"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M23,25.5L247,25.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M108.5,26C108.5,33.03 111.35,39.77 116.41,44.74C121.48,49.71 128.34,52.5 135.5,52.5C142.66,52.5 149.52,49.71 154.59,44.74C159.65,39.77 162.5,33.03 162.5,26"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M99.5,236L99.5,151"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M100.5,236L100.5,151"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M172,150.5H100"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M99.5,151V150"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M172.5,151V150"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M172.5,151L172.5,236"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M171.5,151L171.5,236"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M37.5,236L37.5,170"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M233.5,236L233.5,170"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M163,150.5C163,147.02 162.32,143.57 160.98,140.36C159.65,137.14 157.7,134.22 155.24,131.76C152.78,129.3 149.86,127.35 146.64,126.02C143.43,124.68 139.98,124 136.5,124C133.02,124 129.57,124.68 126.36,126.02C123.14,127.35 120.22,129.3 117.76,131.76C115.3,134.22 113.35,137.14 112.02,140.36C110.68,143.57 110,147.02 110,150.5L136.5,150.5L163,150.5Z"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M110,150.5C110,153.98 110.68,157.43 112.02,160.64C113.35,163.86 115.3,166.78 117.76,169.24C120.22,171.7 123.14,173.65 126.36,174.98C129.57,176.32 133.02,177 136.5,177C139.98,177 143.43,176.32 146.64,174.98C149.86,173.65 152.78,171.7 155.24,169.24C157.7,166.78 159.65,163.86 160.98,160.64C162.32,157.43 163,153.98 163,150.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M177,173.5L172,173.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M177,185.5L172,185.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M177,197.5L172,197.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M177,209.5L172,209.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M100,173.5L95,173.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M100,185.5L95,185.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M100,197.5L95,197.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M100,209.5L95,209.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M233.61,168.79C225.61,149.95 211.97,133.85 194.43,122.57C176.9,111.29 156.27,105.35 135.21,105.5C114.15,105.65 93.62,111.9 76.26,123.43C58.9,134.96 45.51,151.24 37.81,170.2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M233.5,169V168"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M233.5,170V169"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M118.5,218.5C118.5,214 120.34,209.67 123.62,206.48C126.9,203.29 131.35,201.5 136,201.5C140.65,201.5 145.1,203.29 148.38,206.48C151.66,209.67 153.5,214 153.5,218.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M136.5,221.5m0,3a3,3 0,1 1,0 -6a3,3 0,1 1,0 6"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M149,225.5L123,225.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M37.5,171V170"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M38.15,169.77C38.12,169.78 38.14,169.9 38.13,169.93C38.13,170.02 38.07,170.11 38.07,170.21C38.07,170.24 38.04,170.33 38.03,170.37C38.01,170.42 37.97,170.48 37.97,170.54C37.96,170.6 37.91,170.63 37.89,170.69C37.87,170.73 37.84,170.8 37.81,170.82C37.78,170.84 37.76,170.94 37.74,170.97C37.69,171.04 37.63,171.12 37.59,171.19"
|
||||
android:strokeWidth="0.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M37.99,169.07C37.99,169.09 37.99,169.11 37.99,169.13C37.99,169.16 37.98,169.19 37.96,169.21C37.89,169.32 37.8,169.4 37.72,169.5C37.62,169.61 37.51,169.73 37.45,169.87C37.42,169.95 37.39,170.04 37.36,170.12C37.35,170.15 37.31,170.21 37.33,170.25"
|
||||
android:strokeWidth="0.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
</vector>
|
@ -0,0 +1,301 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="567dp"
|
||||
android:height="269dp"
|
||||
android:viewportWidth="567"
|
||||
android:viewportHeight="269">
|
||||
<path
|
||||
android:pathData="M73,24L495,24"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M494,23L494,247"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M495,248L73,248"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M72,249L72,23"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M283.5,23L283.5,247"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M283.5,135.5m-27,0a27,27 0,1 1,54 0a27,27 0,1 1,-54 0"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M73,99.5L158,99.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M73,100.5L158,100.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M158.5,172V100"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M158,99.5H159"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M158,172.5H159"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M158,172.5L73,172.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M158,171.5L73,171.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M73,37.5L139,37.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M73,233.5L139,233.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M158.5,163C161.98,163 165.43,162.32 168.64,160.98C171.86,159.65 174.78,157.7 177.24,155.24C179.7,152.78 181.65,149.86 182.98,146.64C184.32,143.43 185,139.98 185,136.5C185,133.02 184.32,129.57 182.98,126.36C181.65,123.14 179.7,120.22 177.24,117.76C174.78,115.3 171.86,113.35 168.64,112.02C165.43,110.68 161.98,110 158.5,110L158.5,136.5L158.5,163Z"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M158.5,110C155.02,110 151.57,110.68 148.36,112.02C145.14,113.35 142.22,115.3 139.76,117.76C137.3,120.22 135.35,123.14 134.02,126.36C132.68,129.57 132,133.02 132,136.5C132,139.98 132.68,143.43 134.02,146.64C135.35,149.86 137.3,152.78 139.76,155.24C142.22,157.7 145.14,159.65 148.36,160.98C151.57,162.32 155.02,163 158.5,163"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M135.5,177L135.5,172"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M123.5,177L123.5,172"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M111.5,177L111.5,172"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M99.5,177L99.5,172"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M135.5,100L135.5,95"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M123.5,100L123.5,95"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M111.5,100L111.5,95"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M99.5,100L99.5,95"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M140.21,233.61C159.05,225.61 175.15,211.97 186.43,194.43C197.71,176.9 203.65,156.27 203.5,135.21C203.35,114.15 197.1,93.62 185.57,76.26C174.04,58.9 157.76,45.51 138.8,37.81"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M140,233.5H141"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M139,233.5H140"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M90.5,118.5C95,118.5 99.33,120.34 102.52,123.62C105.71,126.9 107.5,131.35 107.5,136C107.5,140.65 105.71,145.1 102.52,148.38C99.33,151.66 95,153.5 90.5,153.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M87.5,136.5m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M83.5,149L83.5,123"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M494,99.5L409,99.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M494,100.5L409,100.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M408.5,172V100"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M409,99.5H408"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M409,172.5H408"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M409,172.5L494,172.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M409,171.5L494,171.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M494,37.5L428,37.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M494,233.5L428,233.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M408.5,163C405.02,163 401.57,162.32 398.36,160.98C395.14,159.65 392.22,157.7 389.76,155.24C387.3,152.78 385.35,149.86 384.02,146.64C382.68,143.43 382,139.98 382,136.5C382,133.02 382.68,129.57 384.02,126.36C385.35,123.14 387.3,120.22 389.76,117.76C392.22,115.3 395.14,113.35 398.36,112.02C401.57,110.68 405.02,110 408.5,110L408.5,136.5L408.5,163Z"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M408.5,110C411.98,110 415.43,110.68 418.64,112.02C421.86,113.35 424.78,115.3 427.24,117.76C429.7,120.22 431.65,123.14 432.98,126.36C434.32,129.57 435,133.02 435,136.5C435,139.98 434.32,143.43 432.98,146.64C431.65,149.86 429.7,152.78 427.24,155.24C424.78,157.7 421.86,159.65 418.64,160.98C415.43,162.32 411.98,163 408.5,163"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M431.5,177L431.5,172"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M443.5,177L443.5,172"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M455.5,177L455.5,172"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M467.5,177L467.5,172"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M431.5,100L431.5,95"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M443.5,100L443.5,95"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M455.5,100L455.5,95"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M467.5,100L467.5,95"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M426.79,233.61C407.95,225.61 391.85,211.97 380.57,194.43C369.3,176.9 363.35,156.27 363.5,135.21C363.65,114.15 369.9,93.62 381.43,76.26C392.95,58.9 409.24,45.51 428.2,37.81"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M427,233.5H426"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M428,233.5H427"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M476.5,118.5C472,118.5 467.67,120.34 464.48,123.62C461.29,126.9 459.5,131.35 459.5,136C459.5,140.65 461.29,145.1 464.48,148.38C467.67,151.66 472,153.5 476.5,153.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M479.5,136.5m3,0a3,3 0,1 0,-6 0a3,3 0,1 0,6 0"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M483.5,149L483.5,123"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M138,37.5H139"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M139.23,38.15C139.22,38.12 139.1,38.14 139.07,38.13C138.98,38.13 138.89,38.07 138.79,38.07C138.76,38.07 138.67,38.04 138.63,38.03C138.58,38.01 138.52,37.97 138.46,37.97C138.4,37.96 138.37,37.91 138.31,37.89C138.27,37.87 138.2,37.84 138.18,37.81C138.16,37.78 138.06,37.76 138.03,37.74C137.96,37.69 137.88,37.63 137.81,37.59"
|
||||
android:strokeWidth="0.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M139.93,37.99C139.91,37.99 139.89,37.99 139.87,37.99C139.84,37.99 139.81,37.98 139.79,37.96C139.68,37.89 139.6,37.8 139.5,37.72C139.39,37.62 139.27,37.51 139.13,37.45C139.05,37.42 138.96,37.39 138.88,37.36C138.85,37.35 138.79,37.31 138.75,37.33"
|
||||
android:strokeWidth="0.5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000000"/>
|
||||
</vector>
|
After Width: | Height: | Size: 5.6 KiB |
Loading…
Reference in new issue