Add custom bet status visuals

pull/5/head
avalin 10 months ago
parent cdcca58940
commit a35e8a1d6b

@ -29,6 +29,7 @@ data class AllInColors(
val allInDarkBlue: Color, val allInDarkBlue: Color,
val allInBarPurple: Color, val allInBarPurple: Color,
val allInBarPink: Color, val allInBarPink: Color,
val allInBarViolet: Color,
val allInBetFinish: Color, val allInBetFinish: Color,
val allInBetInProgress: Color, val allInBetInProgress: Color,
val allInBetWaiting: Color, val allInBetWaiting: Color,
@ -64,6 +65,7 @@ internal val LocalColors = staticCompositionLocalOf {
allInLoginPurple = Color.Unspecified, allInLoginPurple = Color.Unspecified,
allInBarPurple = Color.Unspecified, allInBarPurple = Color.Unspecified,
allInBarPink = Color.Unspecified, allInBarPink = Color.Unspecified,
allInBarViolet = Color.Unspecified,
allInBlue = Color.Unspecified, allInBlue = Color.Unspecified,
allInMint = Color.Unspecified, allInMint = Color.Unspecified,
allInDarkBlue = Color.Unspecified, allInDarkBlue = Color.Unspecified,

@ -38,6 +38,7 @@ fun AllInTheme(
allInLoginPurple = Color(0xFF7F7BFB), allInLoginPurple = Color(0xFF7F7BFB),
allInBarPurple = Color(0xFF846AC9), allInBarPurple = Color(0xFF846AC9),
allInBarPink = Color(0xFFFE2B8A), allInBarPink = Color(0xFFFE2B8A),
allInBarViolet = Color(0xFFC249A8),
allInBlue = Color(0xFF6a89fa), allInBlue = Color(0xFF6a89fa),
allInMint = Color(0xFFC4DEE9), allInMint = Color(0xFFC4DEE9),
allInDarkBlue = Color(0xFF323078), allInDarkBlue = Color(0xFF323078),

@ -13,7 +13,7 @@ import fr.iut.alldev.allin.ui.core.AllInTextIcon
import fr.iut.alldev.allin.ui.core.IconPosition import fr.iut.alldev.allin.ui.core.IconPosition
@Composable @Composable
fun YesNoDetailsLine( fun BinaryDetailsLine(
icon: Painter, icon: Painter,
yesText: String, yesText: String,
noText: String, noText: String,
@ -46,7 +46,7 @@ fun YesNoDetailsLine(
@Composable @Composable
private fun YesNoDetailsLinePreview() { private fun YesNoDetailsLinePreview() {
AllInTheme { AllInTheme {
YesNoDetailsLine( BinaryDetailsLine(
icon = AllInTheme.icons.allCoins(), icon = AllInTheme.icons.allCoins(),
yesText = "550", yesText = "550",
noText = "330" noText = "330"

@ -56,15 +56,15 @@ fun BinaryStatBar(
} }
private class YesNoStatBarPreviewProvider : PreviewParameterProvider<Float> { private class BinaryStatBarPreviewProvider : PreviewParameterProvider<Float> {
override val values = sequenceOf(0f, .33f, .5f, .66f, 1f) override val values = sequenceOf(0f, .33f, .5f, .66f, 1f)
} }
@Preview @Preview
@Composable @Composable
private fun YesNoStatBarPreview( private fun BinaryStatBarPreview(
@PreviewParameter(YesNoStatBarPreviewProvider::class) percentage: Float, @PreviewParameter(BinaryStatBarPreviewProvider::class) percentage: Float,
) { ) {
AllInTheme { AllInTheme {
BinaryStatBar( BinaryStatBar(

@ -0,0 +1,61 @@
package fr.iut.alldev.allin.ui.betStatus.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.tooling.preview.Preview
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInTextIcon
import fr.iut.alldev.allin.ui.core.IconPosition
@Composable
fun SimpleDetailsLine(
icon: Painter,
text: String,
isWin: Boolean
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
AllInTextIcon(
text = text,
color = if (isWin) {
AllInTheme.colors.allInBarViolet
} else {
AllInTheme.colors.allInBlue
},
icon = icon,
position = IconPosition.TRAILING,
size = 15,
iconSize = 15
)
}
}
@Preview
@Composable
private fun SimpleDetailsLineWinPreview() {
AllInTheme {
SimpleDetailsLine(
icon = AllInTheme.icons.allCoins(),
text = "550",
isWin = true
)
}
}
@Preview
@Composable
private fun SimpleDetailsLinePreview() {
AllInTheme {
SimpleDetailsLine(
icon = AllInTheme.icons.allCoins(),
text = "550",
isWin = false
)
}
}

@ -0,0 +1,121 @@
package fr.iut.alldev.allin.ui.betStatus.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.AbsoluteRoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.ext.toPercentageString
import fr.iut.alldev.allin.theme.AllInTheme
@Composable
fun SimpleStatBar(
percentage: Float,
response: String,
isWin: Boolean,
modifier: Modifier = Modifier,
) {
Column(modifier) {
Text(
text = response,
color = if (isWin) {
AllInTheme.colors.allInBarPink
} else {
AllInTheme.colors.allInBlue
},
style = AllInTheme.typography.sm2,
fontStyle = FontStyle.Italic,
fontSize = 18.sp
)
Row(verticalAlignment = Alignment.CenterVertically) {
Box(
modifier = Modifier
.height(20.dp)
.let { itModifier ->
if (percentage != 0f && !percentage.isNaN()) itModifier.weight(percentage)
else itModifier
}
.clip(
AbsoluteRoundedCornerShape(
topLeftPercent = 50,
bottomLeftPercent = 50,
topRightPercent = 0,
bottomRightPercent = 0
)
)
.background(
if (isWin) {
AllInTheme.colors.allInBar2ndGradient
} else {
AllInTheme.colors.allInBar1stGradient
}
)
)
Icon(
painter = painterResource(id = R.drawable.fire_solid),
tint = if (isWin) {
AllInTheme.colors.allInBarViolet
} else {
AllInTheme.colors.allInBarPurple
},
contentDescription = null,
modifier = Modifier.size(32.dp).offset((-7).dp)
)
Text(
modifier = Modifier.let { itModifier ->
if (percentage != 1f && !percentage.isNaN()) itModifier.weight(1 - percentage)
else itModifier
},
text = percentage.toPercentageString(),
style = AllInTheme.typography.h1.copy(
fontSize = if (isWin) 24.sp else 16.sp
),
color = if (isWin) {
AllInTheme.colors.allInBarViolet
} else {
AllInTheme.colors.allInBarPurple
}
)
}
}
}
@Preview
@Composable
private fun SimpleStatBarBarWinPreview() {
AllInTheme {
SimpleStatBar(
percentage = .8f,
response = "Answer",
isWin = true
)
}
}
@Preview
@Composable
private fun SimpleStatBarBarPreview() {
AllInTheme {
SimpleStatBar(
percentage = .4f,
response = "Answer",
isWin = false
)
}
}

@ -18,7 +18,9 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.EmojiEvents import androidx.compose.material.icons.filled.EmojiEvents
import androidx.compose.material.icons.filled.People import androidx.compose.material.icons.filled.People
@ -49,6 +51,7 @@ import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.ext.formatToMediumDateNoYear import fr.iut.alldev.allin.data.ext.formatToMediumDateNoYear
import fr.iut.alldev.allin.data.ext.formatToTime import fr.iut.alldev.allin.data.ext.formatToTime
import fr.iut.alldev.allin.data.model.bet.BetStatus import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.data.model.bet.CustomBet
import fr.iut.alldev.allin.data.model.bet.MatchBet import fr.iut.alldev.allin.data.model.bet.MatchBet
import fr.iut.alldev.allin.data.model.bet.NO_VALUE import fr.iut.alldev.allin.data.model.bet.NO_VALUE
import fr.iut.alldev.allin.data.model.bet.YES_VALUE import fr.iut.alldev.allin.data.model.bet.YES_VALUE
@ -59,8 +62,10 @@ import fr.iut.alldev.allin.ext.getDateEndLabelId
import fr.iut.alldev.allin.ext.getDateStartLabelId import fr.iut.alldev.allin.ext.getDateStartLabelId
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.betStatus.components.BetStatusWinner import fr.iut.alldev.allin.ui.betStatus.components.BetStatusWinner
import fr.iut.alldev.allin.ui.betStatus.components.BinaryDetailsLine
import fr.iut.alldev.allin.ui.betStatus.components.BinaryStatBar import fr.iut.alldev.allin.ui.betStatus.components.BinaryStatBar
import fr.iut.alldev.allin.ui.betStatus.components.YesNoDetailsLine import fr.iut.alldev.allin.ui.betStatus.components.SimpleDetailsLine
import fr.iut.alldev.allin.ui.betStatus.components.SimpleStatBar
import fr.iut.alldev.allin.ui.core.AllInCoinCount import fr.iut.alldev.allin.ui.core.AllInCoinCount
import fr.iut.alldev.allin.ui.core.AllInDetailsDrawer import fr.iut.alldev.allin.ui.core.AllInDetailsDrawer
import fr.iut.alldev.allin.ui.core.ProfilePicture import fr.iut.alldev.allin.ui.core.ProfilePicture
@ -75,19 +80,10 @@ class BetStatusBottomSheetBetDisplayer(
val openParticipateSheet: () -> Unit val openParticipateSheet: () -> Unit
) : BetDisplayer { ) : BetDisplayer {
@Composable @Composable
private fun DisplayBinaryBet( private fun DisplayBet(
betDetail: BetDetail, betDetail: BetDetail,
response1: String, statBar: LazyListScope.() -> Unit
response2: String,
response1Display: String = response1,
response2Display: String = response2
) { ) {
val configuration = LocalConfiguration.current
val locale = remember { ConfigurationCompat.getLocales(configuration).get(0) ?: Locale.getDefault() }
val response1Answer = remember { betDetail.getAnswerOfResponse(response1) }
val response2Answer = remember { betDetail.getAnswerOfResponse(response2) }
Box(Modifier) { Box(Modifier) {
Column { Column {
Column(Modifier.padding(horizontal = 20.dp)) { Column(Modifier.padding(horizontal = 20.dp)) {
@ -119,7 +115,7 @@ class BetStatusBottomSheetBetDisplayer(
} }
if (betDetail.bet.betStatus == BetStatus.FINISHED) { if (betDetail.bet.betStatus == BetStatus.FINISHED) {
BetStatusWinner( BetStatusWinner(
answer = response1Display, answer = YES_VALUE,
color = AllInTheme.colors.allInBlue, color = AllInTheme.colors.allInBlue,
coinAmount = 442, coinAmount = 442,
username = "Imri", username = "Imri",
@ -139,51 +135,23 @@ class BetStatusBottomSheetBetDisplayer(
source: NestedScrollSource source: NestedScrollSource
) = available.copy(x = 0f) ) = available.copy(x = 0f)
}), }),
contentPadding = WindowInsets.navigationBars.asPaddingValues(horizontal = 20.dp) contentPadding = WindowInsets.navigationBars.asPaddingValues(top = 20.dp, start = 20.dp, end = 20.dp)
) { ) {
item {
Spacer(modifier = Modifier.height(20.dp)) statBar(this)
BinaryStatBar(
response1Percentage = remember { if (betDetail.participations.isNotEmpty() || betDetail.userParticipation != null) {
val total = (response1Answer?.totalParticipants item {
?: 0) + (response2Answer?.totalParticipants ?: 0) Text(
if (total == 0) .5f else (response1Answer?.totalParticipants text = stringResource(id = R.string.bet_status_participants_list),
?: 0) / total.toFloat() fontSize = 20.sp,
}, color = AllInTheme.themeColors.onMainSurface,
response1 = response1Display, style = AllInTheme.typography.h1,
response2 = response2Display modifier = Modifier.padding(vertical = 36.dp)
)
AllInDetailsDrawer {
YesNoDetailsLine(
icon = AllInTheme.icons.allCoins(),
yesText = response1Answer?.totalStakes?.toString() ?: "0",
noText = response2Answer?.totalStakes?.toString() ?: "0"
)
YesNoDetailsLine(
icon = rememberVectorPainter(image = Icons.Filled.People),
yesText = response1Answer?.totalParticipants?.toString() ?: "0",
noText = response2Answer?.totalParticipants?.toString() ?: "0"
)
YesNoDetailsLine(
icon = rememberVectorPainter(image = Icons.Filled.WorkspacePremium),
yesText = response1Answer?.highestStake?.toString() ?: "0",
noText = response2Answer?.highestStake?.toString() ?: "0"
)
YesNoDetailsLine(
icon = rememberVectorPainter(image = Icons.Filled.EmojiEvents),
yesText = "x${response1Answer?.odds?.formatToSimple(locale) ?: "1.00"}",
noText = "x${response2Answer?.odds?.formatToSimple(locale) ?: "1.00"}"
) )
} }
Text(
text = stringResource(id = R.string.bet_status_participants_list),
fontSize = 20.sp,
color = AllInTheme.themeColors.onMainSurface,
style = AllInTheme.typography.h1,
modifier = Modifier.padding(vertical = 36.dp)
)
} }
betDetail.userParticipation?.let { betDetail.userParticipation?.let {
item { item {
BetStatusParticipant( BetStatusParticipant(
@ -233,30 +201,143 @@ class BetStatusBottomSheetBetDisplayer(
} }
} }
private fun LazyListScope.displayBinaryStatBar(
betDetail: BetDetail,
response1: String,
response2: String,
response1Display: @Composable () -> String = { response1 },
response2Display: @Composable () -> String = { response2 }
) {
item {
val configuration = LocalConfiguration.current
val locale = remember { ConfigurationCompat.getLocales(configuration).get(0) ?: Locale.getDefault() }
val response1Answer = remember { betDetail.getAnswerOfResponse(response1) }
val response2Answer = remember { betDetail.getAnswerOfResponse(response2) }
BinaryStatBar(
response1Percentage = remember {
response1Answer?.let { betDetail.getPercentageOfAnswer(response1Answer) } ?: 0f
},
response1 = response1Display(),
response2 = response2Display()
)
AllInDetailsDrawer {
BinaryDetailsLine(
icon = AllInTheme.icons.allCoins(),
yesText = response1Answer?.totalStakes?.toString() ?: "0",
noText = response2Answer?.totalStakes?.toString() ?: "0"
)
BinaryDetailsLine(
icon = rememberVectorPainter(image = Icons.Filled.People),
yesText = response1Answer?.totalParticipants?.toString() ?: "0",
noText = response2Answer?.totalParticipants?.toString() ?: "0"
)
BinaryDetailsLine(
icon = rememberVectorPainter(image = Icons.Filled.WorkspacePremium),
yesText = response1Answer?.highestStake?.toString() ?: "0",
noText = response2Answer?.highestStake?.toString() ?: "0"
)
BinaryDetailsLine(
icon = rememberVectorPainter(image = Icons.Filled.EmojiEvents),
yesText = "x${response1Answer?.odds?.formatToSimple(locale) ?: "1.00"}",
noText = "x${response2Answer?.odds?.formatToSimple(locale) ?: "1.00"}"
)
}
}
}
private fun LazyListScope.displayMultiStatBar(
betDetail: BetDetail,
responses: List<String>
) {
val responsesWithDetail = responses.mapNotNull {
betDetail.getAnswerOfResponse(it)
}.associateWith {
betDetail.getPercentageOfAnswer(it)
}
itemsIndexed(responsesWithDetail.toList().sortedByDescending { it.second }) { idx, (answer, percentage) ->
val isWin = remember { idx == 0 }
val configuration = LocalConfiguration.current
val locale = remember { ConfigurationCompat.getLocales(configuration).get(0) ?: Locale.getDefault() }
SimpleStatBar(
percentage = percentage,
response = answer.response,
isWin = isWin
)
AllInDetailsDrawer {
SimpleDetailsLine(
icon = AllInTheme.icons.allCoins(),
text = answer.totalStakes.toString(),
isWin = isWin
)
SimpleDetailsLine(
icon = rememberVectorPainter(image = Icons.Filled.People),
text = answer.totalParticipants.toString(),
isWin = isWin
)
SimpleDetailsLine(
icon = rememberVectorPainter(image = Icons.Filled.WorkspacePremium),
text = answer.highestStake.toString(),
isWin = isWin
)
SimpleDetailsLine(
icon = rememberVectorPainter(image = Icons.Filled.EmojiEvents),
text = "x${answer.odds.formatToSimple(locale)}",
isWin = isWin
)
}
}
}
@Composable @Composable
override fun DisplayYesNoBet(betDetail: BetDetail) { override fun DisplayYesNoBet(betDetail: BetDetail) {
DisplayBinaryBet( DisplayBet(betDetail = betDetail) {
betDetail = betDetail, displayBinaryStatBar(
response1 = YES_VALUE, betDetail = betDetail,
response2 = NO_VALUE, response1 = YES_VALUE,
response1Display = stringResource(id = R.string.Yes).uppercase(), response2 = NO_VALUE,
response2Display = stringResource(id = R.string.No).uppercase() response1Display = { stringResource(id = R.string.Yes).uppercase() },
) response2Display = { stringResource(id = R.string.No).uppercase() }
)
}
} }
@Composable @Composable
override fun DisplayMatchBet(betDetail: BetDetail) { override fun DisplayMatchBet(betDetail: BetDetail) {
val bet = remember { betDetail.bet as MatchBet } val matchBet = remember { betDetail.bet as MatchBet }
DisplayBinaryBet(
betDetail = betDetail, DisplayBet(betDetail = betDetail) {
response1 = bet.nameTeam1, displayBinaryStatBar(
response2 = bet.nameTeam2 betDetail = betDetail,
) response1 = matchBet.nameTeam1,
response2 = matchBet.nameTeam2
)
}
} }
@Composable @Composable
override fun DisplayCustomBet(betDetail: BetDetail) { override fun DisplayCustomBet(betDetail: BetDetail) {
Text("This is a CUSTOM BET") val customBet = remember { betDetail.bet as CustomBet }
DisplayBet(betDetail = betDetail) {
if (customBet.possibleAnswers.size == 2) {
displayBinaryStatBar(
betDetail = betDetail,
response1 = customBet.possibleAnswers.first(),
response2 = customBet.possibleAnswers.last()
)
} else {
displayMultiStatBar(
betDetail = betDetail,
responses = customBet.getResponses()
)
}
}
} }
} }

@ -4,17 +4,17 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.AbsoluteRoundedCornerShape import androidx.compose.foundation.shape.AbsoluteRoundedCornerShape
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -24,58 +24,60 @@ import fr.iut.alldev.allin.theme.AllInTheme
@Composable @Composable
fun StatBar( fun StatBar(
percentage: Float, percentage: Float,
modifier: Modifier = Modifier,
leadingBrush: Brush = AllInTheme.colors.allInBar1stGradient,
trailingBrush: Brush = AllInTheme.colors.allInBar2ndGradient,
icon: (@Composable () -> Unit)? = null,
) { ) {
val radius100percent = if (percentage == 1f) 50 else 0 Box(modifier = modifier) {
val radius0percent = if (percentage == 0f) 50 else 0
Box {
Row( Row(
Modifier.align(Alignment.Center) modifier = Modifier.align(Alignment.Center),
verticalAlignment = Alignment.CenterVertically
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
.height(20.dp) .height(20.dp)
.fillMaxWidth(percentage) .fillMaxWidth(percentage)
.padding(end = if (percentage == 1f) 25.dp else 0.dp)
.clip( .clip(
AbsoluteRoundedCornerShape( AbsoluteRoundedCornerShape(
topLeftPercent = 50, topLeftPercent = 50,
bottomLeftPercent = 50, bottomLeftPercent = 50,
topRightPercent = radius100percent, topRightPercent = 0,
bottomRightPercent = radius100percent bottomRightPercent = 0
) )
) )
.background(AllInTheme.colors.allInBar1stGradient) .background(leadingBrush)
) )
if (percentage != 0f && percentage != 1f) {
Spacer(modifier = Modifier.width(15.dp))
}
Box( Box(
modifier = Modifier modifier = Modifier
.height(20.dp) .height(20.dp)
.fillMaxWidth() .fillMaxWidth()
.padding(start = if (percentage == 0f) 25.dp else 15.dp)
.clip( .clip(
AbsoluteRoundedCornerShape( AbsoluteRoundedCornerShape(
topLeftPercent = radius0percent, topLeftPercent = 0,
bottomLeftPercent = radius0percent, bottomLeftPercent = 0,
topRightPercent = 50, topRightPercent = 50,
bottomRightPercent = 50 bottomRightPercent = 50
) )
) )
.background(AllInTheme.colors.allInBar2ndGradient) .background(trailingBrush)
) )
} }
PercentagePositionnedElement(percentage = percentage) { PercentagePositionnedElement(percentage = percentage) {
when (percentage) { icon?.invoke() ?: when (percentage) {
0f -> Icon( 0f -> Icon(
painter = painterResource(id = R.drawable.fire_solid), painter = painterResource(id = R.drawable.fire_solid),
tint = AllInTheme.colors.allInBarPink, tint = AllInTheme.colors.allInPink,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(32.dp) modifier = Modifier.size(32.dp)
) )
1f -> Icon( 1f -> Icon(
painter = painterResource(id = R.drawable.fire_solid), painter = painterResource(id = R.drawable.fire_solid),
tint = AllInTheme.colors.allInBarPurple, tint = AllInTheme.colors.allInPurple,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(32.dp) modifier = Modifier.size(32.dp)
) )

@ -1,9 +1,12 @@
package fr.iut.alldev.allin.ui.preview package fr.iut.alldev.allin.ui.preview
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import fr.iut.alldev.allin.data.model.bet.CustomBet
import fr.iut.alldev.allin.data.model.bet.MatchBet
import fr.iut.alldev.allin.data.model.bet.NO_VALUE import fr.iut.alldev.allin.data.model.bet.NO_VALUE
import fr.iut.alldev.allin.data.model.bet.Participation import fr.iut.alldev.allin.data.model.bet.Participation
import fr.iut.alldev.allin.data.model.bet.YES_VALUE import fr.iut.alldev.allin.data.model.bet.YES_VALUE
import fr.iut.alldev.allin.data.model.bet.YesNoBet
import fr.iut.alldev.allin.data.model.bet.vo.BetAnswerDetail import fr.iut.alldev.allin.data.model.bet.vo.BetAnswerDetail
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
@ -11,22 +14,70 @@ class BetDetailPreviewProvider : PreviewParameterProvider<BetDetail> {
override val values = BetWithStatusPreviewProvider().values.map { override val values = BetWithStatusPreviewProvider().values.map {
BetDetail( BetDetail(
bet = it, bet = it,
answers = listOf( answers = when (it) {
BetAnswerDetail( is CustomBet -> listOf(
response = YES_VALUE, BetAnswerDetail(
totalStakes = 300, response = "Answer 1",
totalParticipants = 2, totalStakes = 300,
highestStake = 200, totalParticipants = 8,
odds = 1.0f highestStake = 200,
), odds = 1.0f
BetAnswerDetail( ),
response = NO_VALUE, BetAnswerDetail(
totalStakes = 150, response = "Answer 2",
totalParticipants = 1, totalStakes = 300,
highestStake = 150, totalParticipants = 4,
odds = 2.0f highestStake = 200,
odds = 1.0f
),
BetAnswerDetail(
response = "Answer 3",
totalStakes = 300,
totalParticipants = 2,
highestStake = 200,
odds = 1.0f
),
BetAnswerDetail(
response = "Answer 4",
totalStakes = 300,
totalParticipants = 1,
highestStake = 200,
odds = 1.0f
)
) )
), is MatchBet -> listOf(
BetAnswerDetail(
response = "The Monarchs",
totalStakes = 300,
totalParticipants = 2,
highestStake = 200,
odds = 1.0f
),
BetAnswerDetail(
response = "Climate Change",
totalStakes = 150,
totalParticipants = 1,
highestStake = 150,
odds = 2.0f
)
)
is YesNoBet -> listOf(
BetAnswerDetail(
response = YES_VALUE,
totalStakes = 300,
totalParticipants = 2,
highestStake = 200,
odds = 1.0f
),
BetAnswerDetail(
response = NO_VALUE,
totalStakes = 150,
totalParticipants = 1,
highestStake = 150,
odds = 2.0f
)
)
},
participations = listOf( participations = listOf(
Participation( Participation(
betId = it.id, betId = it.id,

@ -0,0 +1,346 @@
package fr.iut.alldev.allin.data.api
import fr.iut.alldev.allin.data.api.interceptors.AllInAPIException
import fr.iut.alldev.allin.data.api.model.CheckUser
import fr.iut.alldev.allin.data.api.model.RequestBet
import fr.iut.alldev.allin.data.api.model.RequestParticipation
import fr.iut.alldev.allin.data.api.model.RequestUser
import fr.iut.alldev.allin.data.api.model.ResponseBet
import fr.iut.alldev.allin.data.api.model.ResponseBetAnswerDetail
import fr.iut.alldev.allin.data.api.model.ResponseBetDetail
import fr.iut.alldev.allin.data.api.model.ResponseBetResultDetail
import fr.iut.alldev.allin.data.api.model.ResponseParticipation
import fr.iut.alldev.allin.data.api.model.ResponseUser
import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.data.model.bet.BetType
import fr.iut.alldev.allin.data.model.bet.NO_VALUE
import fr.iut.alldev.allin.data.model.bet.YES_VALUE
import fr.iut.alldev.allin.data.model.bet.vo.BetResult
import java.time.ZonedDateTime
import java.util.UUID
class MockAllInApi : AllInApi {
private fun getUserFromToken(token: String) =
mockUsers.find { it.first.token == token.removePrefix("Bearer ") }
private fun getAnswerDetails(
bet: ResponseBet,
participations: List<ResponseParticipation>
): List<ResponseBetAnswerDetail> {
return bet.response.map { response ->
val responseParticipations = participations.filter { it.answer == response }
ResponseBetAnswerDetail(
response = response,
totalStakes = responseParticipations.sumOf { it.stake },
totalParticipants = responseParticipations.size,
highestStake = responseParticipations.maxOfOrNull { it.stake } ?: 0,
odds = if (participations.isEmpty()) 0.0f else responseParticipations.size / participations.size.toFloat()
)
}
}
override suspend fun login(body: CheckUser): ResponseUser {
return mockUsers.find { it.first.username == body.login && it.second == body.password }?.first
?: throw AllInAPIException("Invalid login/password.")
}
override suspend fun login(token: String): ResponseUser {
return getUserFromToken(token)?.first
?: throw AllInAPIException("Invalid token")
}
override suspend fun register(body: RequestUser): ResponseUser {
val response = ResponseUser(
id = UUID.randomUUID().toString(),
username = body.username,
email = body.email,
nbCoins = 500,
token = "${body.username} ${mockUsers.size}"
) to body.password
mockUsers.add(response)
return response.first
}
override suspend fun createBet(token: String, body: RequestBet) {
mockBets.add(
ResponseBet(
id = UUID.randomUUID().toString(),
theme = body.theme,
sentenceBet = body.sentenceBet,
endRegistration = body.endRegistration,
endBet = body.endBet,
isPrivate = body.isPrivate,
response = body.response,
type = BetType.BINARY,
status = BetStatus.WAITING,
createdBy = ""
)
)
}
override suspend fun getAllBets(token: String): List<ResponseBet> {
getUserFromToken(token) ?: throw AllInAPIException("Invalid login/password.")
return mockBets
}
override suspend fun getToConfirm(token: String): List<ResponseBetDetail> {
val user = getUserFromToken(token) ?: throw AllInAPIException("Invalid login/password.")
return mockBets.filter {
it.createdBy == user.first.username && it.status == BetStatus.CLOSING
}.map { bet ->
val betParticipations = mockParticipations.filter { it.betId == bet.id }
val userParticipation = betParticipations.find { it.username == user.first.username }
ResponseBetDetail(
bet = bet,
answers = getAnswerDetails(bet, betParticipations),
participations = betParticipations,
userParticipation = userParticipation
)
}
}
override suspend fun confirmBet(token: String, id: String, value: String) {
getUserFromToken(token) ?: throw AllInAPIException("Invalid login/password.")
val bet = mockBets.find { it.id == id } ?: throw AllInAPIException("Unauthorized")
mockResults.add(
BetResult(
betId = id,
result = value
)
)
mockBets[mockBets.indexOf(bet)] = bet.copy(status = BetStatus.FINISHED)
}
override suspend fun getBet(token: String, id: String): ResponseBetDetail {
val bet = mockBets.find { it.id == id } ?: throw AllInAPIException("Bet not found")
val user = getUserFromToken(token) ?: throw AllInAPIException("Invalid login/password.")
val betParticipations = mockParticipations.filter { it.betId == bet.id }
val userParticipation = betParticipations.find { it.username == user.first.username }
return ResponseBetDetail(
bet = bet,
answers = getAnswerDetails(bet, betParticipations),
participations = betParticipations,
userParticipation = userParticipation
)
}
override suspend fun getBetCurrent(token: String): List<ResponseBetDetail> {
return emptyList()
}
override suspend fun getBetHistory(token: String): List<ResponseBetResultDetail> {
return emptyList()
}
override suspend fun getWon(token: String): List<ResponseBetResultDetail> {
return emptyList()
}
override suspend fun participateToBet(token: String, body: RequestParticipation) {
getUserFromToken(token)?.let {
mockParticipations.add(
ResponseParticipation(
id = "",
betId = body.betId,
username = it.first.username,
answer = body.answer,
stake = body.stake
)
)
} ?: throw AllInAPIException("Invalid token")
}
companion object {
private val mockUsers = mutableListOf(
ResponseUser(
id = "UUID 1",
username = "User 1",
email = "john@doe.fr",
nbCoins = 250,
token = "token 1"
) to "12345",
ResponseUser(
id = "UUID 2",
username = "User 2",
email = "john@doe.fr",
nbCoins = 250,
token = "token 2"
) to "12345",
ResponseUser(
id = "UUID 3",
username = "User 3",
email = "john@doe.fr",
nbCoins = 250,
token = "token 3"
) to "12345",
ResponseUser(
id = "UUID 4",
username = "User 4",
email = "john@doe.fr",
nbCoins = 250,
token = "token 4"
) to "12345",
ResponseUser(
id = "UUID 5",
username = "User 5",
email = "john@doe.fr",
nbCoins = 250,
token = "token 5"
) to "12345",
ResponseUser(
id = "UUID 6",
username = "User 6",
email = "john@doe.fr",
nbCoins = 250,
token = "token 6"
) to "12345",
ResponseUser(
id = "UUID 7",
username = "User 7",
email = "john@doe.fr",
nbCoins = 250,
token = "token 7"
) to "12345"
)
private val mockParticipations = mutableListOf(
ResponseParticipation(
id = "",
betId = "UUID1",
username = mockUsers[1].first.username,
answer = NO_VALUE,
stake = 1500
),
ResponseParticipation(
id = "",
betId = "UUID1",
username = mockUsers[2].first.username,
answer = YES_VALUE,
stake = 300
),
ResponseParticipation(
id = "",
betId = "UUID1",
username = mockUsers[3].first.username,
answer = YES_VALUE,
stake = 25
),
ResponseParticipation(
id = "",
betId = "UUID1",
username = mockUsers[4].first.username,
answer = NO_VALUE,
stake = 222
),
ResponseParticipation(
id = "",
betId = "UUID1",
username = mockUsers[5].first.username,
answer = NO_VALUE,
stake = 222
),
ResponseParticipation(
id = "",
betId = "UUID1",
username = mockUsers[6].first.username,
answer = NO_VALUE,
stake = 222
),
ResponseParticipation(
id = "",
betId = "UUID2",
username = mockUsers[0].first.username,
answer = "Answer 1",
stake = 200
),
ResponseParticipation(
id = "",
betId = "UUID2",
username = mockUsers[2].first.username,
answer = "Answer 1",
stake = 200
),
ResponseParticipation(
id = "",
betId = "UUID2",
username = mockUsers[3].first.username,
answer = "Answer 2",
stake = 200
),
ResponseParticipation(
id = "",
betId = "UUID2",
username = mockUsers[4].first.username,
answer = "Answer 3",
stake = 100
),
ResponseParticipation(
id = "",
betId = "UUID2",
username = mockUsers[5].first.username,
answer = "Answer 3",
stake = 400
),
ResponseParticipation(
id = "",
betId = "UUID2",
username = mockUsers[6].first.username,
answer = "Answer 1",
stake = 50
),
ResponseParticipation(
id = "",
betId = "UUID3",
username = mockUsers[1].first.username,
answer = "The Monarchs",
stake = 420
)
)
private val mockBets = mutableListOf(
ResponseBet(
id = "UUID1",
theme = "Études",
sentenceBet = "Dave va arriver en retard demain matin ?",
endRegistration = ZonedDateTime.now().plusDays(3),
endBet = ZonedDateTime.now().plusDays(4),
isPrivate = false,
response = listOf(YES_VALUE, NO_VALUE),
createdBy = "Armure",
type = BetType.BINARY,
status = BetStatus.IN_PROGRESS,
),
ResponseBet(
id = "UUID2",
theme = "Études",
sentenceBet = "Quoi ?",
endRegistration = ZonedDateTime.now().plusDays(3),
endBet = ZonedDateTime.now().plusDays(4),
isPrivate = false,
response = listOf("Answer 1", "Answer 2", "Answer 3", "Answer 4"),
createdBy = "User 2",
type = BetType.CUSTOM,
status = BetStatus.IN_PROGRESS,
),
ResponseBet(
id = "UUID3",
theme = "Sport",
sentenceBet = "Quelle équipe va gagner ?",
endRegistration = ZonedDateTime.now().minusDays(3),
endBet = ZonedDateTime.now().minusDays(2),
isPrivate = false,
response = listOf("The Monarchs", "Climate Change"),
createdBy = "User 1",
type = BetType.MATCH,
status = BetStatus.IN_PROGRESS,
)
)
private val mockResults by lazy { mutableListOf<BetResult>() }
}
}

@ -1,294 +0,0 @@
package fr.iut.alldev.allin.data.api
import fr.iut.alldev.allin.data.api.interceptors.AllInAPIException
import fr.iut.alldev.allin.data.api.model.CheckUser
import fr.iut.alldev.allin.data.api.model.RequestBet
import fr.iut.alldev.allin.data.api.model.RequestParticipation
import fr.iut.alldev.allin.data.api.model.RequestUser
import fr.iut.alldev.allin.data.api.model.ResponseBet
import fr.iut.alldev.allin.data.api.model.ResponseBetAnswerDetail
import fr.iut.alldev.allin.data.api.model.ResponseBetDetail
import fr.iut.alldev.allin.data.api.model.ResponseBetResultDetail
import fr.iut.alldev.allin.data.api.model.ResponseParticipation
import fr.iut.alldev.allin.data.api.model.ResponseUser
import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.data.model.bet.BetType
import fr.iut.alldev.allin.data.model.bet.NO_VALUE
import fr.iut.alldev.allin.data.model.bet.YES_VALUE
import fr.iut.alldev.allin.data.model.bet.vo.BetResult
import java.time.ZonedDateTime
import java.util.UUID
class MockAllInApi : AllInApi {
private fun getUserFromToken(token: String) =
mockUsers.find { it.first.token == token.removePrefix("Bearer ") }
private fun getAnswerDetails(
bet: ResponseBet,
participations: List<ResponseParticipation>
): List<ResponseBetAnswerDetail> {
return bet.response.map { response ->
val responseParticipations = participations.filter { it.answer == response }
ResponseBetAnswerDetail(
response = response,
totalStakes = responseParticipations.sumOf { it.stake },
totalParticipants = responseParticipations.size,
highestStake = responseParticipations.maxOfOrNull { it.stake } ?: 0,
odds = if (participations.isEmpty()) 0.0f else responseParticipations.size / participations.size.toFloat()
)
}
}
override suspend fun login(body: CheckUser): ResponseUser {
return mockUsers.find { it.first.username == body.login && it.second == body.password }?.first
?: throw AllInAPIException("Invalid login/password.")
}
override suspend fun login(token: String): ResponseUser {
return getUserFromToken(token)?.first
?: throw AllInAPIException("Invalid token")
}
override suspend fun register(body: RequestUser): ResponseUser {
val response = ResponseUser(
id = UUID.randomUUID().toString(),
username = body.username,
email = body.email,
nbCoins = 500,
token = "${body.username} ${mockUsers.size}"
) to body.password
mockUsers.add(response)
return response.first
}
override suspend fun createBet(token: String, body: RequestBet) {
mockBets.add(
ResponseBet(
id = UUID.randomUUID().toString(),
theme = body.theme,
sentenceBet = body.sentenceBet,
endRegistration = body.endRegistration,
endBet = body.endBet,
isPrivate = body.isPrivate,
response = body.response,
type = BetType.BINARY,
status = BetStatus.WAITING,
createdBy = ""
)
)
}
override suspend fun getAllBets(token: String): List<ResponseBet> {
getUserFromToken(token) ?: throw AllInAPIException("Invalid login/password.")
return mockBets
}
override suspend fun getToConfirm(token: String): List<ResponseBetDetail> {
val user = getUserFromToken(token) ?: throw AllInAPIException("Invalid login/password.")
return mockBets.filter {
it.createdBy == user.first.username && it.status == BetStatus.CLOSING
}.map { bet ->
val betParticipations = mockParticipations.filter { it.betId == bet.id }
val userParticipation = betParticipations.find { it.username == user.first.username }
ResponseBetDetail(
bet = bet,
answers = getAnswerDetails(bet, betParticipations),
participations = betParticipations,
userParticipation = userParticipation
)
}
}
override suspend fun confirmBet(token: String, id: String, value: String) {
getUserFromToken(token) ?: throw AllInAPIException("Invalid login/password.")
val bet = mockBets.find { it.id == id } ?: throw AllInAPIException("Unauthorized")
mockResults.add(
BetResult(
betId = id,
result = value
)
)
mockBets[mockBets.indexOf(bet)] = bet.copy(status = BetStatus.FINISHED)
}
override suspend fun getBet(token: String, id: String): ResponseBetDetail {
val bet = mockBets.find { it.id == id } ?: throw AllInAPIException("Bet not found")
val user = getUserFromToken(token) ?: throw AllInAPIException("Invalid login/password.")
val betParticipations = mockParticipations.filter { it.betId == bet.id }
val userParticipation = betParticipations.find { it.username == user.first.username }
return ResponseBetDetail(
bet = bet,
answers = getAnswerDetails(bet, betParticipations),
participations = betParticipations,
userParticipation = userParticipation
)
}
override suspend fun getBetCurrent(token: String): List<ResponseBetDetail> {
return emptyList()
}
override suspend fun getBetHistory(token: String): List<ResponseBetResultDetail> {
return emptyList()
}
override suspend fun getWon(token: String): List<ResponseBetResultDetail> {
return emptyList()
}
override suspend fun participateToBet(token: String, body: RequestParticipation) {
getUserFromToken(token)?.let {
mockParticipations.add(
ResponseParticipation(
id = "",
betId = body.betId,
username = it.first.username,
answer = body.answer,
stake = body.stake
)
)
} ?: throw AllInAPIException("Invalid token")
}
}
private val mockUsers = mutableListOf(
ResponseUser(
id = "UUID 1",
username = "User 1",
email = "john@doe.fr",
nbCoins = 250,
token = "token 1"
) to "12345",
ResponseUser(
id = "UUID 2",
username = "User 2",
email = "john@doe.fr",
nbCoins = 250,
token = "token 2"
) to "12345",
ResponseUser(
id = "UUID 3",
username = "User 3",
email = "john@doe.fr",
nbCoins = 250,
token = "token 3"
) to "12345",
ResponseUser(
id = "UUID 4",
username = "User 4",
email = "john@doe.fr",
nbCoins = 250,
token = "token 4"
) to "12345",
ResponseUser(
id = "UUID 5",
username = "User 5",
email = "john@doe.fr",
nbCoins = 250,
token = "token 5"
) to "12345",
ResponseUser(
id = "UUID 6",
username = "User 6",
email = "john@doe.fr",
nbCoins = 250,
token = "token 6"
) to "12345",
ResponseUser(
id = "UUID 7",
username = "User 7",
email = "john@doe.fr",
nbCoins = 250,
token = "token 7"
) to "12345"
)
private val mockParticipations = mutableListOf(
ResponseParticipation(
id = "",
betId = "UUID1",
username = mockUsers[1].first.username,
answer = NO_VALUE,
stake = 1500
),
ResponseParticipation(
id = "",
betId = "UUID1",
username = mockUsers[2].first.username,
answer = YES_VALUE,
stake = 300
),
ResponseParticipation(
id = "",
betId = "UUID1",
username = mockUsers[3].first.username,
answer = YES_VALUE,
stake = 25
),
ResponseParticipation(
id = "",
betId = "UUID1",
username = mockUsers[4].first.username,
answer = NO_VALUE,
stake = 222
),
ResponseParticipation(
id = "",
betId = "UUID1",
username = mockUsers[5].first.username,
answer = NO_VALUE,
stake = 222
),
ResponseParticipation(
id = "",
betId = "UUID1",
username = mockUsers[6].first.username,
answer = NO_VALUE,
stake = 222
)
)
private val mockBets = mutableListOf(
ResponseBet(
id = "UUID1",
theme = "Études",
sentenceBet = "Dave va arriver en retard demain matin ?",
endRegistration = ZonedDateTime.now().plusDays(3),
endBet = ZonedDateTime.now().plusDays(4),
isPrivate = false,
response = listOf(YES_VALUE, NO_VALUE),
createdBy = "Armure",
type = BetType.BINARY,
status = BetStatus.IN_PROGRESS,
),
ResponseBet(
id = "UUID2",
theme = "Études",
sentenceBet = "Dave va arriver en retard demain matin ?",
endRegistration = ZonedDateTime.now().plusDays(3),
endBet = ZonedDateTime.now().plusDays(4),
isPrivate = false,
response = listOf("Answer 1", "Answer 2", "Answer 3", "Answer 4"),
createdBy = "User 2",
type = BetType.BINARY,
status = BetStatus.IN_PROGRESS,
),
ResponseBet(
id = "UUID3",
theme = "Sport",
sentenceBet = "Nouveau record du monde ?",
endRegistration = ZonedDateTime.now().minusDays(3),
endBet = ZonedDateTime.now().minusDays(2),
isPrivate = false,
response = listOf(YES_VALUE, NO_VALUE),
createdBy = "User 1",
type = BetType.BINARY,
status = BetStatus.CLOSING,
)
)
private val mockResults by lazy { mutableListOf<BetResult>() }

@ -7,6 +7,7 @@ import fr.iut.alldev.allin.data.model.bet.BetResultDetail
import fr.iut.alldev.allin.data.model.bet.BetStatus import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.data.model.bet.BetType import fr.iut.alldev.allin.data.model.bet.BetType
import fr.iut.alldev.allin.data.model.bet.CustomBet import fr.iut.alldev.allin.data.model.bet.CustomBet
import fr.iut.alldev.allin.data.model.bet.MatchBet
import fr.iut.alldev.allin.data.model.bet.NO_VALUE import fr.iut.alldev.allin.data.model.bet.NO_VALUE
import fr.iut.alldev.allin.data.model.bet.YES_VALUE import fr.iut.alldev.allin.data.model.bet.YES_VALUE
import fr.iut.alldev.allin.data.model.bet.YesNoBet import fr.iut.alldev.allin.data.model.bet.YesNoBet
@ -30,9 +31,9 @@ data class ResponseBet(
var response: List<String>, var response: List<String>,
val createdBy: String val createdBy: String
) { ) {
fun toBet(): Bet { fun toBet(): Bet = when {
if (response.toSet() == setOf(YES_VALUE, NO_VALUE)) { response.toSet() == setOf(YES_VALUE, NO_VALUE) -> {
return YesNoBet( YesNoBet(
id = id ?: "", id = id ?: "",
theme = theme, theme = theme,
phrase = sentenceBet, phrase = sentenceBet,
@ -42,8 +43,25 @@ data class ResponseBet(
betStatus = status, betStatus = status,
creator = createdBy creator = createdBy
) )
} else { }
return CustomBet(
type == BetType.MATCH -> {
MatchBet(
id = id ?: "",
theme = theme,
phrase = sentenceBet,
endRegisterDate = endRegistration,
endBetDate = endBet,
isPublic = !isPrivate,
betStatus = status,
creator = createdBy,
nameTeam1 = response.firstOrNull() ?: "",
nameTeam2 = response.lastOrNull() ?: ""
)
}
else -> {
CustomBet(
id = id ?: "", id = id ?: "",
theme = theme, theme = theme,
phrase = sentenceBet, phrase = sentenceBet,
@ -56,6 +74,7 @@ data class ResponseBet(
) )
} }
} }
} }
@Keep @Keep

@ -11,4 +11,13 @@ data class BetDetail(
) { ) {
fun getAnswerOfResponse(response: String) = fun getAnswerOfResponse(response: String) =
answers.find { it.response == response } answers.find { it.response == response }
fun getPercentageOfAnswer(answerDetail: BetAnswerDetail): Float =
(answerDetail.totalParticipants.toFloat() / answers.sumOf { it.totalParticipants }).let {
if (it.isNaN() || it.isInfinite()) {
1f / this.answers.size
} else {
it
}
}
} }
Loading…
Cancel
Save