Mock API and Bet participation bottom sheet
continuous-integration/drone/push Build is passing Details

pull/3/head
Arthur VALIN 1 year ago
parent 3c5545bdb6
commit 2432599726

@ -37,7 +37,7 @@ object Bets {
isPublic = true, isPublic = true,
betStatus = BetStatus.InProgress, betStatus = BetStatus.InProgress,
creator = "creator", creator = "creator",
possibleAnswers = setOf( possibleAnswers = listOf(
"Answer 1", "Answer 1",
"Answer 2", "Answer 2",
"Answer 3", "Answer 3",

@ -12,17 +12,17 @@ import fr.iut.alldev.allin.vo.bet.BetDisplayer
class BetTestDisplayer : BetDisplayer { class BetTestDisplayer : BetDisplayer {
@Composable @Composable
override fun DisplayYesNoBet(b: YesNoBet) { override fun DisplayYesNoBet(bet: YesNoBet) {
Text("This is a YesNo Bet", Modifier.testTag(TestTags.YES_NO_BET.tag)) Text("This is a YesNo Bet", Modifier.testTag(TestTags.YES_NO_BET.tag))
} }
@Composable @Composable
override fun DisplayMatchBet(b: MatchBet) { override fun DisplayMatchBet(bet: MatchBet) {
Text("This is a Match Bet", Modifier.testTag(TestTags.MATCH_BET.tag)) Text("This is a Match Bet", Modifier.testTag(TestTags.MATCH_BET.tag))
} }
@Composable @Composable
override fun DisplayCustomBet(b: CustomBet) { override fun DisplayCustomBet(bet: CustomBet) {
Text("This is a Custom Bet", Modifier.testTag(TestTags.CUSTOM_BET.tag)) Text("This is a Custom Bet", Modifier.testTag(TestTags.CUSTOM_BET.tag))
} }
} }

@ -12,7 +12,7 @@ import fr.iut.alldev.allin.data.model.bet.BetType
@StringRes @StringRes
fun BetType.getTitleId(): Int { fun BetType.getTitleId(): Int {
return when (this) { return when (this) {
BetType.YES_NO -> R.string.yes_no BetType.YES_NO -> R.string.yes_no
BetType.MATCH -> R.string.sport_match BetType.MATCH -> R.string.sport_match
BetType.CUSTOM -> R.string.custom_answers BetType.CUSTOM -> R.string.custom_answers
} }

@ -0,0 +1,9 @@
package fr.iut.alldev.allin.ext
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.util.Locale
fun Float.formatToSimple(locale: Locale): String {
return DecimalFormat("0.##", DecimalFormatSymbols.getInstance(locale)).format(this)
}

@ -0,0 +1,21 @@
package fr.iut.alldev.allin.ext
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.util.Locale
fun String.verifyIsFloat(locale: Locale): String? {
val pattern = Regex("^\\d+(\\.|\\,)?\\d*\$")
val decimalSeparator = DecimalFormatSymbols.getInstance(locale).decimalSeparator.toString()
return if (this.matches(pattern)) {
this.replace(Regex("[.,]"), decimalSeparator).format()
} else if (this.isEmpty()) {
this
} else null
}
fun String.toFloatOrNull(locale: Locale): Float? {
val format = DecimalFormat("0.##", DecimalFormatSymbols.getInstance(locale))
return format.parse(this)?.toFloat()
}

@ -118,7 +118,7 @@ class BetCreationViewModel @Inject constructor(
isPublic = isPublic.value, isPublic = isPublic.value,
nameTeam1 = "", nameTeam1 = "",
nameTeam2 = "", nameTeam2 = "",
possibleAnswers = setOf(), possibleAnswers = listOf(),
creator = currentUser.username creator = currentUser.username
) )
betRepository.createBet(bet, keystoreManager.getToken() ?: "") betRepository.createBet(bet, keystoreManager.getToken() ?: "")

@ -21,25 +21,25 @@ fun BetCreationScreenQuestionTab(
nbFriends: Int, nbFriends: Int,
betTheme: String, betTheme: String,
betThemeError: String?, betThemeError: String?,
setBetTheme: (String)->Unit, setBetTheme: (String) -> Unit,
betPhrase: String, betPhrase: String,
betPhraseError: String?, betPhraseError: String?,
setBetPhrase: (String)->Unit, setBetPhrase: (String) -> Unit,
isPublic: Boolean, isPublic: Boolean,
setIsPublic: (Boolean)->Unit, setIsPublic: (Boolean) -> Unit,
registerDate: ZonedDateTime, registerDate: ZonedDateTime,
registerDateError: String?, registerDateError: String?,
betDate: ZonedDateTime, betDate: ZonedDateTime,
betDateError: String?, betDateError: String?,
selectedFriends: MutableList<Int>, selectedFriends: MutableList<Int>,
setRegisterDateDialog: (Boolean)->Unit, setRegisterDateDialog: (Boolean) -> Unit,
setEndDateDialog: (Boolean)->Unit, setEndDateDialog: (Boolean) -> Unit,
setRegisterTimeDialog: (Boolean)->Unit, setRegisterTimeDialog: (Boolean) -> Unit,
setEndTimeDialog: (Boolean)->Unit, setEndTimeDialog: (Boolean) -> Unit,
interactionSource: MutableInteractionSource interactionSource: MutableInteractionSource
) { ) {
val bringIntoViewRequester = remember { BringIntoViewRequester() } val bringIntoViewRequester = remember { BringIntoViewRequester() }
Column(modifier){ Column(modifier) {
QuestionTabThemePhraseSection( QuestionTabThemePhraseSection(
betTheme = betTheme, betTheme = betTheme,
betThemeError = betThemeError, betThemeError = betThemeError,
@ -47,7 +47,6 @@ fun BetCreationScreenQuestionTab(
betPhrase = betPhrase, betPhrase = betPhrase,
betPhraseError = betPhraseError, betPhraseError = betPhraseError,
setBetPhrase = setBetPhrase, setBetPhrase = setBetPhrase,
bringIntoViewRequester = bringIntoViewRequester,
interactionSource = interactionSource interactionSource = interactionSource
) )
Spacer(modifier = Modifier.height(35.dp)) Spacer(modifier = Modifier.height(35.dp))

@ -6,7 +6,6 @@ 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.padding
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.HelpOutline import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -24,11 +23,10 @@ import fr.iut.alldev.allin.ui.core.AllInTitleInfo
internal fun QuestionTabThemePhraseSection( internal fun QuestionTabThemePhraseSection(
betTheme: String, betTheme: String,
betThemeError: String?, betThemeError: String?,
setBetTheme: (String)->Unit, setBetTheme: (String) -> Unit,
betPhrase: String, betPhrase: String,
betPhraseError: String?, betPhraseError: String?,
setBetPhrase: (String)->Unit, setBetPhrase: (String) -> Unit,
bringIntoViewRequester: BringIntoViewRequester,
interactionSource: MutableInteractionSource interactionSource: MutableInteractionSource
) { ) {
AllInTitleInfo( AllInTitleInfo(
@ -41,13 +39,12 @@ internal fun QuestionTabThemePhraseSection(
AllInTextField( AllInTextField(
placeholder = stringResource(id = R.string.Theme_placeholder), placeholder = stringResource(id = R.string.Theme_placeholder),
value = betTheme, value = betTheme,
onValueChange = setBetTheme, modifier = Modifier.fillMaxWidth(),
bringIntoViewRequester = bringIntoViewRequester,
borderColor = AllInTheme.colors.white,
maxChar = 20, maxChar = 20,
placeholderFontSize = 13.sp, placeholderFontSize = 13.sp,
onValueChange = setBetTheme,
errorText = betThemeError, errorText = betThemeError,
modifier = Modifier.fillMaxWidth() borderColor = AllInTheme.colors.white
) )
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
AllInTitleInfo( AllInTitleInfo(
@ -60,15 +57,14 @@ internal fun QuestionTabThemePhraseSection(
AllInTextField( AllInTextField(
placeholder = stringResource(id = R.string.Bet_Phrase_placeholder), placeholder = stringResource(id = R.string.Bet_Phrase_placeholder),
value = betPhrase, value = betPhrase,
borderColor = AllInTheme.colors.white, modifier = Modifier
onValueChange = setBetPhrase, .fillMaxWidth()
bringIntoViewRequester = bringIntoViewRequester, .height(100.dp),
multiLine = true,
maxChar = 100, maxChar = 100,
placeholderFontSize = 13.sp, placeholderFontSize = 13.sp,
multiLine = true,
onValueChange = setBetPhrase,
errorText = betPhraseError, errorText = betPhraseError,
modifier = Modifier borderColor = AllInTheme.colors.white
.fillMaxWidth()
.height(100.dp)
) )
} }

@ -7,7 +7,10 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import fr.iut.alldev.allin.data.model.bet.Bet import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.ui.betStatus.components.BetStatusBottomSheetBack import fr.iut.alldev.allin.ui.betStatus.components.BetStatusBottomSheetBack
import fr.iut.alldev.allin.ui.betStatus.components.BetStatusParticipationBottomSheet
import fr.iut.alldev.allin.ui.betStatus.components.getParticipationAnswers
import fr.iut.alldev.allin.ui.core.AllInBottomSheet import fr.iut.alldev.allin.ui.core.AllInBottomSheet
@ -21,7 +24,12 @@ fun BetStatusBottomSheet(
sheetVisibility: Boolean, sheetVisibility: Boolean,
sheetBackVisibility: Boolean, sheetBackVisibility: Boolean,
bet: Bet?, bet: Bet?,
onDismiss: ()->Unit, paddingValues: PaddingValues,
userCoinAmount: MutableIntState,
onParticipate: (Int) -> Unit,
onDismiss: () -> Unit,
participateSheetVisibility: Boolean,
setParticipateSheetVisibility: (Boolean) -> Unit,
displayBet: @Composable (Bet) -> Unit displayBet: @Composable (Bet) -> Unit
) { ) {
AnimatedVisibility( AnimatedVisibility(
@ -45,13 +53,41 @@ fun BetStatusBottomSheet(
onDismiss = onDismiss, onDismiss = onDismiss,
state = state, state = state,
scrimColor = Color.Transparent scrimColor = Color.Transparent
){ ) {
var selectedAnswer by remember { mutableStateOf(0) }
var stake by remember { mutableStateOf<Int?>(null) }
Column( Column(
Modifier.fillMaxHeight(SHEET_HEIGHT) Modifier.fillMaxHeight(SHEET_HEIGHT)
) { ) {
bet?.let { bet?.let {
val elements = bet.getParticipationAnswers()
displayBet(it) displayBet(it)
BetStatusParticipationBottomSheet(
sheetVisibility = participateSheetVisibility && bet.betStatus == BetStatus.Waiting && state.hasExpandedState,
safeBottomPadding = paddingValues.calculateBottomPadding(),
betPhrase = bet.phrase,
coinAmount = userCoinAmount.intValue,
onDismiss = { setParticipateSheetVisibility(false) },
state = rememberModalBottomSheetState(skipPartiallyExpanded = true),
elements = elements,
selectedElement = elements.getOrNull(selectedAnswer),
stake = stake,
setStake = { stake = it },
setElement = { idx -> selectedAnswer = idx },
enabled = stake != null &&
(stake ?: 0) <= userCoinAmount.intValue
) {
stake?.let {
onParticipate(it)
}
}
} }
} }
} }
} }

@ -0,0 +1,110 @@
package fr.iut.alldev.allin.ui.betStatus.components
import android.content.res.Configuration
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.model.bet.Bet
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.YES_VALUE
import fr.iut.alldev.allin.data.model.bet.YesNoBet
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.preview.BetPreviewProvider
private val participationAnswerFontSize = 25.sp
@Composable
fun Bet.getParticipationAnswers(): List<@Composable RowScope.() -> Unit> =
when (this) {
is CustomBet -> this.possibleAnswers.map {
{
Text(
text = it,
color = AllInTheme.colors.allInBlue,
style = AllInTheme.typography.h1,
fontSize = participationAnswerFontSize
)
}
}
is MatchBet -> listOf(
{
Text(
text = this@getParticipationAnswers.nameTeam1,
color = AllInTheme.colors.allInBlue,
style = AllInTheme.typography.h1,
fontSize = participationAnswerFontSize
)
},
{
Text(
text = this@getParticipationAnswers.nameTeam2,
color = AllInTheme.colors.allInBarPink,
style = AllInTheme.typography.h1,
fontSize = participationAnswerFontSize
)
}
)
is YesNoBet -> listOf(
{
Text(
text = stringResource(id = R.string.Yes).uppercase(),
color = AllInTheme.colors.allInBlue,
style = AllInTheme.typography.h1,
fontSize = participationAnswerFontSize
)
},
{
Text(
text = stringResource(id = R.string.No).uppercase(),
color = AllInTheme.colors.allInBarPink,
style = AllInTheme.typography.h1,
fontSize = participationAnswerFontSize
)
}
)
}
fun Bet.getAnswerFromParticipationIdx(idx: Int) =
when (this) {
is CustomBet -> this.possibleAnswers.getOrElse(idx) { "" }
is MatchBet -> when (idx) {
0 -> this.nameTeam1
1 -> this.nameTeam2
else -> ""
}
is YesNoBet -> when (idx) {
0 -> YES_VALUE
1 -> NO_VALUE
else -> ""
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ParticipationAnswersPreview(
@PreviewParameter(BetPreviewProvider::class) bet: Bet,
) {
AllInTheme {
Column {
bet.getParticipationAnswers().forEach {
Row(Modifier.fillMaxWidth()) { it() }
}
}
}
}

@ -1,13 +1,17 @@
package fr.iut.alldev.allin.ui.betStatus.components package fr.iut.alldev.allin.ui.betStatus.components
import android.content.res.Configuration
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@ -16,6 +20,8 @@ import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInBottomSheet import fr.iut.alldev.allin.ui.core.AllInBottomSheet
import fr.iut.alldev.allin.ui.core.AllInButton import fr.iut.alldev.allin.ui.core.AllInButton
import fr.iut.alldev.allin.ui.core.AllInCoinCount import fr.iut.alldev.allin.ui.core.AllInCoinCount
import fr.iut.alldev.allin.ui.core.AllInIntTextField
import fr.iut.alldev.allin.ui.core.AllInSelectionBox
import fr.iut.alldev.allin.ui.core.topbar.AllInTopBarCoinCounter import fr.iut.alldev.allin.ui.core.topbar.AllInTopBarCoinCounter
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -28,8 +34,13 @@ fun BetStatusParticipationBottomSheet(
coinAmount: Int, coinAmount: Int,
onDismiss: () -> Unit, onDismiss: () -> Unit,
state: SheetState, state: SheetState,
onParticipate: () -> Unit, enabled: Boolean,
content: @Composable () -> Unit stake: Int?,
setStake: (Int?) -> Unit,
elements: List<@Composable RowScope.() -> Unit>,
selectedElement: (@Composable RowScope.() -> Unit)?,
setElement: (Int) -> Unit,
onParticipate: () -> Unit
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
AllInBottomSheet( AllInBottomSheet(
@ -37,6 +48,96 @@ fun BetStatusParticipationBottomSheet(
onDismiss = onDismiss, onDismiss = onDismiss,
state = state, state = state,
containerColor = AllInTheme.themeColors.background2 containerColor = AllInTheme.themeColors.background2
) {
BetStatusParticipationBottomSheetContent(
safeBottomPadding = safeBottomPadding,
betPhrase = betPhrase,
coinAmount = coinAmount,
elements = elements,
selectedElement = selectedElement,
setElement = setElement,
enabled = enabled,
stake = stake,
setStake = setStake
) {
scope.launch {
onParticipate()
state.hide()
onDismiss()
}
}
}
}
@Composable
private fun ColumnScope.BetStatusParticipationBottomSheetContent(
safeBottomPadding: Dp,
betPhrase: String,
coinAmount: Int,
enabled: Boolean,
stake: Int?,
setStake: (Int?) -> Unit,
selectedElement: (@Composable RowScope.() -> Unit)?,
elements: List<@Composable RowScope.() -> Unit>,
setElement: (Int) -> Unit,
onButtonClick: () -> Unit
) {
val (answersBoxIsOpen, setAnswersBoxIsOpen) = remember { mutableStateOf(false) }
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = stringResource(id = R.string.place_your_bets),
style = AllInTheme.typography.h2,
color = AllInTheme.themeColors.onMainSurface,
fontSize = 20.sp,
modifier = Modifier.padding(start = 18.dp)
)
AllInTopBarCoinCounter(
amount = coinAmount,
backgroundColor = AllInTheme.colors.allInBlue,
textColor = AllInTheme.colors.white,
iconColor = AllInTheme.colors.white,
)
}
Column(
modifier = Modifier.padding(horizontal = 18.dp)
) {
Text(
text = betPhrase,
style = AllInTheme.typography.p2,
color = AllInTheme.themeColors.onMainSurface,
modifier = Modifier.padding(vertical = 30.dp)
)
AllInSelectionBox(
isOpen = answersBoxIsOpen,
setIsOpen = setAnswersBoxIsOpen,
selected = selectedElement,
setSelected = { setElement(elements.indexOf(it)) },
elements = elements,
borderWidth = 1.dp
)
Spacer(modifier = Modifier.height(8.dp))
AllInIntTextField(
value = stake,
setValue = setStake,
placeholder = stringResource(id = R.string.bet_result_stake),
trailingIcon = AllInTheme.icons.allCoins(),
modifier = Modifier.fillMaxWidth(),
maxChar = null
)
}
Spacer(modifier = Modifier.height(100.dp))
HorizontalDivider(color = AllInTheme.themeColors.border)
Column(
modifier = Modifier
.background(AllInTheme.themeColors.background)
.padding(horizontal = 7.dp)
.padding(bottom = safeBottomPadding, top = 7.dp),
verticalArrangement = Arrangement.spacedBy(7.dp)
) { ) {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@ -44,65 +145,44 @@ fun BetStatusParticipationBottomSheet(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Text( Text(
text = stringResource(id = R.string.place_your_bets), text = stringResource(id = R.string.Possible_winnings),
style = AllInTheme.typography.h2, style = AllInTheme.typography.p1,
color = AllInTheme.themeColors.onMainSurface, color = AllInTheme.themeColors.onBackground
fontSize = 20.sp,
modifier = Modifier.padding(start = 18.dp)
) )
AllInTopBarCoinCounter( AllInCoinCount(
amount = coinAmount, amount = 121,
backgroundColor = AllInTheme.colors.allInBlue, color = AllInTheme.themeColors.onBackground
textColor = AllInTheme.colors.white,
iconColor = AllInTheme.colors.white,
) )
} }
Spacer(modifier = Modifier.height(30.dp)) AllInButton(
Text( enabled = enabled,
text = betPhrase, color = AllInTheme.colors.allInPurple,
style = AllInTheme.typography.p2, text = stringResource(id = R.string.Participate),
color = AllInTheme.themeColors.onMainSurface, textColor = AllInTheme.colors.white,
modifier = Modifier.padding(horizontal = 18.dp) radius = 5.dp,
onClick = onButtonClick
) )
Spacer(modifier = Modifier.height(100.dp)) }
HorizontalDivider(color = AllInTheme.themeColors.border) }
Column(
modifier = Modifier @Preview
.background(AllInTheme.themeColors.background) @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
.padding(horizontal = 7.dp) @Composable
.padding(bottom = safeBottomPadding, top = 7.dp), private fun BetStatusParticipationBottomSheetContentPreview() {
verticalArrangement = Arrangement.spacedBy(7.dp) AllInTheme {
) { Column {
Row( BetStatusParticipationBottomSheetContent(
modifier = Modifier.fillMaxWidth(), safeBottomPadding = 0.dp,
horizontalArrangement = Arrangement.SpaceBetween, betPhrase = "Bet phrase",
verticalAlignment = Alignment.CenterVertically coinAmount = 3620,
) { onButtonClick = {},
Text( elements = emptyList(),
text = stringResource(id = R.string.Possible_winnings), setElement = {},
style = AllInTheme.typography.p1, selectedElement = null,
color = AllInTheme.themeColors.onBackground enabled = true,
) stake = 123,
AllInCoinCount( setStake = {}
amount = 121, )
color = AllInTheme.themeColors.onBackground
)
}
Box(modifier = Modifier.fillMaxSize()) {
content()
}
AllInButton(
color = AllInTheme.colors.allInPurple,
text = stringResource(id = R.string.Participate),
textColor = AllInTheme.colors.white,
radius = 5.dp
) {
scope.launch {
onParticipate()
state.hide()
onDismiss()
}
}
} }
} }
} }

@ -10,7 +10,6 @@ import androidx.compose.material.icons.filled.WorkspacePremium
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -29,7 +28,6 @@ import fr.iut.alldev.allin.data.model.bet.YesNoBet
import fr.iut.alldev.allin.ext.getDateEndLabelId 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.BetStatusParticipationBottomSheet
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.YesNoDetailsLine import fr.iut.alldev.allin.ui.betStatus.components.YesNoDetailsLine
import fr.iut.alldev.allin.ui.betStatus.components.YesNoStatBar import fr.iut.alldev.allin.ui.betStatus.components.YesNoStatBar
@ -41,28 +39,22 @@ import fr.iut.alldev.allin.ui.preview.BetWithStatusPreviewProvider
import fr.iut.alldev.allin.vo.bet.BetDisplayer import fr.iut.alldev.allin.vo.bet.BetDisplayer
class BetStatusBottomSheetBetDisplayer( class BetStatusBottomSheetBetDisplayer(
val userCoinAmount: MutableIntState, val openParticipateSheet: () -> Unit
val onParticipate: (Int) -> Unit,
) : BetDisplayer { ) : BetDisplayer {
val participateBottomSheetVisibility = mutableStateOf(false)
val paddingValues = mutableStateOf(PaddingValues()) val paddingValues = mutableStateOf(PaddingValues())
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
override fun DisplayYesNoBet(b: YesNoBet) { override fun DisplayYesNoBet(bet: YesNoBet) {
val (participateSheetVisibility, setParticipateSheetVisibility) = remember {
this.participateBottomSheetVisibility
}
val safeBottomPadding = paddingValues.value.calculateBottomPadding() val safeBottomPadding = paddingValues.value.calculateBottomPadding()
Box(Modifier.padding(bottom = safeBottomPadding)) { Box(Modifier.padding(bottom = safeBottomPadding)) {
Column { Column {
Column(Modifier.padding(horizontal = 20.dp)) { Column(Modifier.padding(horizontal = 20.dp)) {
BetTitleHeader( BetTitleHeader(
title = b.phrase, title = bet.phrase,
category = b.theme, category = bet.theme,
creator = b.creator, creator = bet.creator,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
@ -70,22 +62,22 @@ class BetStatusBottomSheetBetDisplayer(
horizontalAlignment = Alignment.End horizontalAlignment = Alignment.End
) { ) {
BetDateTimeRow( BetDateTimeRow(
label = stringResource(id = b.betStatus.getDateStartLabelId()), label = stringResource(id = bet.betStatus.getDateStartLabelId()),
date = b.endRegisterDate.formatToMediumDateNoYear(), date = bet.endRegisterDate.formatToMediumDateNoYear(),
time = b.endRegisterDate.formatToTime(), time = bet.endRegisterDate.formatToTime(),
modifier = Modifier.width(IntrinsicSize.Max) modifier = Modifier.width(IntrinsicSize.Max)
) )
Spacer(modifier = Modifier.height(15.dp)) Spacer(modifier = Modifier.height(15.dp))
BetDateTimeRow( BetDateTimeRow(
label = stringResource(id = b.betStatus.getDateEndLabelId()), label = stringResource(id = bet.betStatus.getDateEndLabelId()),
date = b.endBetDate.formatToMediumDateNoYear(), date = bet.endBetDate.formatToMediumDateNoYear(),
time = b.endBetDate.formatToTime(), time = bet.endBetDate.formatToTime(),
modifier = Modifier.width(IntrinsicSize.Max) modifier = Modifier.width(IntrinsicSize.Max)
) )
} }
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
} }
if (b.betStatus is BetStatus.Finished) { if (bet.betStatus is BetStatus.Finished) {
BetStatusWinner( BetStatusWinner(
answer = stringResource(id = R.string.Yes), answer = stringResource(id = R.string.Yes),
color = AllInTheme.colors.allInBlue, color = AllInTheme.colors.allInBlue,
@ -128,42 +120,26 @@ class BetStatusBottomSheetBetDisplayer(
} }
} }
} }
if (b.betStatus !is BetStatus.Finished) { if (bet.betStatus !is BetStatus.Finished) {
RainbowButton( RainbowButton(
modifier = Modifier modifier = Modifier
.align(Alignment.BottomCenter) .align(Alignment.BottomCenter)
.padding(horizontal = 7.dp), .padding(horizontal = 7.dp),
text = stringResource(id = R.string.Participate), text = stringResource(id = R.string.Participate),
enabled = b.betStatus == BetStatus.Waiting, enabled = bet.betStatus == BetStatus.Waiting,
onClick = { onClick = openParticipateSheet
setParticipateSheetVisibility(true)
}
) )
} }
} }
BetStatusParticipationBottomSheet(
sheetVisibility = participateSheetVisibility && b.betStatus == BetStatus.Waiting,
safeBottomPadding = safeBottomPadding,
betPhrase = b.phrase,
coinAmount = userCoinAmount.intValue,
onDismiss = { setParticipateSheetVisibility(false) },
state = rememberModalBottomSheetState(),
onParticipate = {
onParticipate(100)
}
) {
}
} }
@Composable @Composable
override fun DisplayMatchBet(b: MatchBet) { override fun DisplayMatchBet(bet: MatchBet) {
Text("This is a MATCH BET") Text("This is a MATCH BET")
} }
@Composable @Composable
override fun DisplayCustomBet(b: CustomBet) { override fun DisplayCustomBet(bet: CustomBet) {
Text("This is a CUSTOM BET") Text("This is a CUSTOM BET")
} }
} }
@ -176,9 +152,6 @@ private fun BetStatusBottomSheetPreview(
) { ) {
AllInTheme { AllInTheme {
val coins = remember { mutableIntStateOf(100) } val coins = remember { mutableIntStateOf(100) }
BetStatusBottomSheetBetDisplayer( BetStatusBottomSheetBetDisplayer {}.DisplayBet(bet)
userCoinAmount = coins,
onParticipate = {}
).DisplayBet(bet)
} }
} }

@ -15,10 +15,11 @@ import fr.iut.alldev.allin.theme.AllInTheme
@Composable @Composable
fun AllInButton( fun AllInButton(
modifier: Modifier = Modifier,
enabled: Boolean = true,
color: Color, color: Color,
text: String, text: String,
textColor: Color, textColor: Color,
modifier: Modifier = Modifier,
radius: Dp = 15.dp, radius: Dp = 15.dp,
onClick: () -> Unit, onClick: () -> Unit,
) { ) {
@ -26,13 +27,14 @@ fun AllInButton(
onClick = onClick, onClick = onClick,
modifier = modifier, modifier = modifier,
radius = radius, radius = radius,
backgroundColor = color backgroundColor = color,
enabled = enabled
) { ) {
Text( Text(
text = text, text = text,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
style = AllInTheme.typography.h2, style = AllInTheme.typography.h2,
color = textColor, color = if(enabled) textColor else AllInTheme.themeColors.disabledBorder,
fontSize = 20.sp, fontSize = 20.sp,
modifier = Modifier modifier = Modifier
.padding(vertical = 15.dp) .padding(vertical = 15.dp)
@ -51,6 +53,21 @@ private fun AllInButtonPreview() {
textColor = Color.White textColor = Color.White
) { ) {
}
}
}
@Preview
@Composable
private fun AllInButtonDisabledPreview() {
AllInTheme {
AllInButton(
color = AllInTheme.colors.allInLoginPurple,
text = "Connexion",
textColor = Color.White,
enabled = false
) {
} }
} }
} }

@ -6,8 +6,10 @@ import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
@ -29,6 +31,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
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 import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.R import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
@ -36,11 +39,11 @@ import fr.iut.alldev.allin.theme.AllInTheme
class SelectionElement( class SelectionElement(
val textId: Int, val textId: Int,
val imageVector: ImageVector, val imageVector: ImageVector
) )
@Composable @Composable
fun AllInSelectionLine( private fun AllInSelectionLine(
text: String, text: String,
iconVector: ImageVector?, iconVector: ImageVector?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -91,13 +94,19 @@ fun AllInSelectionLine(
fun AllInSelectionBox( fun AllInSelectionBox(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
isOpen: Boolean, isOpen: Boolean,
borderWidth: Dp? = null,
setIsOpen: (Boolean) -> Unit, setIsOpen: (Boolean) -> Unit,
selected: SelectionElement?, selected: SelectionElement?,
setSelected: (SelectionElement) -> Unit, setSelected: (SelectionElement) -> Unit,
elements: List<SelectionElement>, elements: List<SelectionElement>,
) { ) {
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
AllInCard(modifier.fillMaxWidth()) { AllInCard(
modifier = modifier.fillMaxWidth(),
radius = 10.dp,
borderWidth = borderWidth,
borderColor = AllInTheme.colors.allInPurple.copy(alpha = .42f)
) {
Column( Column(
Modifier.animateContentSize() Modifier.animateContentSize()
) { ) {
@ -154,6 +163,84 @@ private fun AllInSelectionBoxClosedPreview() {
} }
} }
@Composable
private fun AllInSelectionLine(
element: @Composable RowScope.() -> Unit,
modifier: Modifier = Modifier,
onClick: () -> Unit,
trailingIcon: ImageVector? = null,
interactionSource: MutableInteractionSource
) {
Row(
modifier = modifier
.fillMaxWidth()
.clickable(
interactionSource = interactionSource,
indication = null,
onClick = onClick
)
.padding(horizontal = 12.dp, vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
element()
trailingIcon?.let {
Icon(
imageVector = trailingIcon,
contentDescription = null,
tint = AllInTheme.colors.allInPurple,
modifier = Modifier
.size(30.dp)
)
}
}
}
@Composable
fun AllInSelectionBox(
modifier: Modifier = Modifier,
isOpen: Boolean,
setIsOpen: (Boolean) -> Unit,
borderWidth: Dp? = null,
selected: (@Composable RowScope.() -> Unit)?,
setSelected: (@Composable RowScope.() -> Unit) -> Unit,
elements: List<@Composable RowScope.() -> Unit>,
) {
val interactionSource = remember { MutableInteractionSource() }
AllInCard(
modifier = modifier.fillMaxWidth(),
radius = 10.dp,
borderWidth = borderWidth,
borderColor = AllInTheme.colors.allInPurple.copy(alpha = .42f)
) {
Column(Modifier.animateContentSize()) {
AllInSelectionLine(
element = selected ?: { Box { } },
onClick = { setIsOpen(!isOpen) },
interactionSource = interactionSource,
trailingIcon = with(Icons.Default) {
if (isOpen) ExpandLess else ExpandMore
}
)
AnimatedVisibility(isOpen) {
Column {
HorizontalDivider(color = AllInTheme.themeColors.border)
elements.filter { it != selected }.forEach { element ->
AllInSelectionLine(
element = element,
interactionSource = interactionSource,
onClick = {
setSelected(element)
setIsOpen(false)
}
)
}
}
}
}
}
}
@Preview @Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable @Composable

@ -2,7 +2,6 @@ package fr.iut.alldev.allin.ui.core
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@ -11,35 +10,37 @@ import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.text.TextRange import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.text.input.* import androidx.compose.ui.text.input.*
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.core.os.ConfigurationCompat
import androidx.core.text.isDigitsOnly
import fr.iut.alldev.allin.ext.formatToSimple
import fr.iut.alldev.allin.ext.toFloatOrNull
import fr.iut.alldev.allin.ext.verifyIsFloat
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
import kotlinx.coroutines.launch
import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape
import java.util.Locale
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) @OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable @Composable
fun AllInTextField( fun AllInTextField(
placeholder: String,
value: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
placeholder: String? = null,
value: String,
maxChar: Int? = null, maxChar: Int? = null,
enabled: Boolean = true, enabled: Boolean = true,
trailingIcon: ImageVector? = null, trailingIcon: Painter? = null,
trailingContent: @Composable (() -> Unit)? = null, trailingContent: @Composable() (() -> Unit)? = null,
placeholderFontSize: TextUnit = 18.sp, placeholderFontSize: TextUnit = 18.sp,
multiLine: Boolean = false, multiLine: Boolean = false,
onValueChange: (String) -> Unit,
errorText: String? = null, errorText: String? = null,
bringIntoViewRequester: BringIntoViewRequester,
visualTransformation: VisualTransformation = VisualTransformation.None, visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardType: KeyboardType = KeyboardType.Text, keyboardType: KeyboardType = KeyboardType.Text,
imeAction: ImeAction = ImeAction.Default, imeAction: ImeAction = ImeAction.Default,
@ -48,58 +49,38 @@ fun AllInTextField(
containerColor: Color = AllInTheme.themeColors.background, containerColor: Color = AllInTheme.themeColors.background,
textColor: Color = AllInTheme.themeColors.onMainSurface, textColor: Color = AllInTheme.themeColors.onMainSurface,
placeholderColor: Color = AllInTheme.themeColors.onBackground2, placeholderColor: Color = AllInTheme.themeColors.onBackground2,
onValueChange: (String) -> Unit,
) { ) {
val scope = rememberCoroutineScope()
var hasFocus by remember { mutableStateOf(false) }
var textFieldValue by remember {
mutableStateOf(TextFieldValue(text = value, selection = TextRange(value.length)))
}
LaunchedEffect(key1 = value, block = {
textFieldValue = TextFieldValue(
text = value,
selection = textFieldValue.selection
)
})
OutlinedTextField( OutlinedTextField(
value = textFieldValue, value = value,
isError = errorText != null, isError = errorText != null,
modifier = modifier modifier = modifier,
.onFocusChanged {
hasFocus = it.hasFocus
if (it.isFocused) {
scope.launch {
bringIntoViewRequester.bringIntoView()
}
}
},
supportingText = errorText?.let { supportingText = errorText?.let {
{ AllInErrorLine(text = it) } { AllInErrorLine(text = it) }
}, },
visualTransformation = visualTransformation, visualTransformation = visualTransformation,
singleLine = !multiLine, singleLine = !multiLine,
onValueChange = { onValueChange = {
if (maxChar == null || it.text.length <= maxChar) { if (maxChar == null || it.length <= maxChar) {
textFieldValue = it onValueChange(it)
onValueChange(it.text)
} }
}, },
placeholder = { placeholder = placeholder?.let {
Text( {
text = placeholder, Text(
fontSize = placeholderFontSize, text = placeholder,
style = AllInTheme.typography.p1, fontSize = placeholderFontSize,
color = placeholderColor, style = AllInTheme.typography.p1,
maxLines = if (multiLine) 3 else 1, color = placeholderColor,
overflow = TextOverflow.Ellipsis maxLines = if (multiLine) 3 else 1,
) overflow = TextOverflow.Ellipsis
)
}
}, },
trailingIcon = trailingContent ?: trailingIcon?.let { trailingIcon = trailingContent ?: trailingIcon?.let {
@Composable { @Composable {
Icon( Icon(
imageVector = it, painter = it,
contentDescription = null, contentDescription = null,
tint = AllInTheme.colors.allInLightGrey300 tint = AllInTheme.colors.allInLightGrey300
) )
@ -135,22 +116,13 @@ fun AllInPasswordField(
keyboardActions: KeyboardActions = KeyboardActions.Default, keyboardActions: KeyboardActions = KeyboardActions.Default,
errorText: String? = null, errorText: String? = null,
onValueChange: (String) -> Unit, onValueChange: (String) -> Unit,
bringIntoViewRequester: BringIntoViewRequester,
isHiddenByDefault: Boolean = true, isHiddenByDefault: Boolean = true,
) { ) {
var hidden by remember { var hidden by remember { mutableStateOf(isHiddenByDefault) }
mutableStateOf(isHiddenByDefault)
}
AllInTextField( AllInTextField(
modifier = modifier,
errorText = errorText,
placeholder = placeholder, placeholder = placeholder,
imeAction = imeAction,
keyboardActions = keyboardActions,
visualTransformation = if (hidden) PasswordVisualTransformation() else VisualTransformation.None,
value = value, value = value,
onValueChange = onValueChange, modifier = modifier,
bringIntoViewRequester = bringIntoViewRequester,
trailingContent = { trailingContent = {
IconButton( IconButton(
onClick = { hidden = !hidden } onClick = { hidden = !hidden }
@ -162,10 +134,73 @@ fun AllInPasswordField(
) )
} }
}, },
keyboardType = keyboardType onValueChange = onValueChange,
errorText = errorText,
visualTransformation = if (hidden) PasswordVisualTransformation() else VisualTransformation.None,
keyboardType = keyboardType,
imeAction = imeAction,
keyboardActions = keyboardActions
) )
} }
@Composable
fun AllInFloatTextfield(
modifier: Modifier = Modifier,
value: Float?,
setValue: (Float?) -> Unit
) {
val configuration = LocalConfiguration.current
val locale = remember {
ConfigurationCompat.getLocales(configuration).get(0) ?: Locale.getDefault()
}
var stringValue by remember(value) { mutableStateOf(value?.formatToSimple(locale) ?: "") }
AllInTextField(
value = stringValue,
modifier = modifier,
maxChar = 5,
keyboardType = KeyboardType.Number
) {
it.verifyIsFloat(locale)?.let {
stringValue = it
if (it.isNotEmpty()) {
if (it.lastOrNull() !in setOf(',', '.')) {
it.toFloatOrNull(locale)?.let {
setValue(it)
}
}
} else setValue(null)
}
}
}
@Composable
fun AllInIntTextField(
modifier: Modifier = Modifier,
placeholder: String? = null,
maxChar: Int? = 3,
value: Int?,
trailingIcon: Painter? = null,
setValue: (Int?) -> Unit
) {
AllInTextField(
value = value?.toString() ?: "",
placeholder = placeholder,
modifier = modifier,
maxChar = maxChar,
trailingIcon = trailingIcon,
keyboardType = KeyboardType.NumberPassword
) {
if (it.isEmpty()) setValue(null)
else if (it.isDigitsOnly()) {
it.toIntOrNull()?.let {
setValue(it)
}
}
}
}
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Preview @Preview
@Composable @Composable
@ -174,8 +209,7 @@ private fun AllInTextFieldPlaceholderPreview() {
AllInTextField( AllInTextField(
placeholder = "Email", placeholder = "Email",
value = "", value = "",
onValueChange = { }, onValueChange = { }
bringIntoViewRequester = BringIntoViewRequester()
) )
} }
} }
@ -190,8 +224,7 @@ private fun AllInTextFieldValuePreview() {
AllInTextField( AllInTextField(
placeholder = "Email", placeholder = "Email",
value = "JohnDoe@mail.com", value = "JohnDoe@mail.com",
onValueChange = { }, onValueChange = { }
bringIntoViewRequester = BringIntoViewRequester()
) )
} }
} }
@ -204,9 +237,8 @@ private fun AllInTextFieldErrorPreview() {
AllInTextField( AllInTextField(
placeholder = "Email", placeholder = "Email",
value = "JohnDoe@mail.com", value = "JohnDoe@mail.com",
errorText = "This is an error.",
onValueChange = { }, onValueChange = { },
bringIntoViewRequester = BringIntoViewRequester() errorText = "This is an error."
) )
} }
} }
@ -220,13 +252,11 @@ private fun AllInTextFieldPasswordPreview() {
AllInPasswordField( AllInPasswordField(
placeholder = "Password", placeholder = "Password",
value = value, value = value,
onValueChange = setValue, onValueChange = setValue
bringIntoViewRequester = BringIntoViewRequester()
) )
} }
} }
@OptIn(ExperimentalFoundationApi::class)
@Preview @Preview
@Composable @Composable
private fun AllInTextFieldMultilinePreview() { private fun AllInTextFieldMultilinePreview() {
@ -234,9 +264,8 @@ private fun AllInTextFieldMultilinePreview() {
AllInTextField( AllInTextField(
placeholder = "David sera il absent le Lundi matin en cours ?", placeholder = "David sera il absent le Lundi matin en cours ?",
value = "", value = "",
onValueChange = { },
multiLine = true, multiLine = true,
bringIntoViewRequester = BringIntoViewRequester() onValueChange = { }
) )
} }
} }

@ -1,6 +1,5 @@
package fr.iut.alldev.allin.ui.login package fr.iut.alldev.allin.ui.login
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -11,12 +10,10 @@ import androidx.compose.foundation.layout.fillMaxSize
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.padding
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -42,7 +39,6 @@ import fr.iut.alldev.allin.ui.core.AllInLoading
import fr.iut.alldev.allin.ui.core.AllInPasswordField import fr.iut.alldev.allin.ui.core.AllInPasswordField
import fr.iut.alldev.allin.ui.core.AllInTextField import fr.iut.alldev.allin.ui.core.AllInTextField
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable @Composable
fun LoginScreen( fun LoginScreen(
navigateToDashboard: () -> Unit, navigateToDashboard: () -> Unit,
@ -51,7 +47,6 @@ fun LoginScreen(
) { ) {
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
val bringIntoViewRequester = BringIntoViewRequester()
val loading by remember { loginViewModel.loading } val loading by remember { loginViewModel.loading }
var hasLoginError by remember { loginViewModel.hasError } var hasLoginError by remember { loginViewModel.hasError }
@ -102,22 +97,20 @@ fun LoginScreen(
verticalArrangement = Arrangement.spacedBy(20.dp) verticalArrangement = Arrangement.spacedBy(20.dp)
) { ) {
AllInTextField( AllInTextField(
modifier = Modifier.fillMaxWidth(),
placeholder = stringResource(id = R.string.username), placeholder = stringResource(id = R.string.username),
value = username, value = username,
modifier = Modifier.fillMaxWidth(),
onValueChange = setUsername, onValueChange = setUsername,
bringIntoViewRequester = bringIntoViewRequester,
imeAction = ImeAction.Next, imeAction = ImeAction.Next,
keyboardActions = keyboardActions keyboardActions = keyboardActions
) )
AllInPasswordField( AllInPasswordField(
modifier = Modifier.fillMaxWidth(),
placeholder = stringResource(id = R.string.password), placeholder = stringResource(id = R.string.password),
value = password, value = password,
onValueChange = setPassword, modifier = Modifier.fillMaxWidth(),
bringIntoViewRequester = bringIntoViewRequester,
imeAction = ImeAction.Done, imeAction = ImeAction.Done,
keyboardActions = keyboardActions keyboardActions = keyboardActions,
onValueChange = setPassword
) )
} }
ClickableText( ClickableText(

@ -70,14 +70,15 @@ fun MainScreen(
val (selectedBet, setSelectedBet) = remember { mainViewModel.selectedBet } val (selectedBet, setSelectedBet) = remember { mainViewModel.selectedBet }
val (wonBet, setWonBet) = remember { mainViewModel.wonBet } val (wonBet, setWonBet) = remember { mainViewModel.wonBet }
val (statusVisibility, sheetBackVisibility, setStatusVisibility) = rememberBetStatusVisibilities() val (statusVisibility, sheetBackVisibility, setStatusVisibility) = rememberBetStatusVisibilities()
val (displayResult, setDisplayResult ) = remember{ mutableStateOf(true) } val (participateSheetVisibility, setParticipateSheetVisibility) = remember {
mutableStateOf(false)
}
val (displayResult, setDisplayResult) = remember { mutableStateOf(true) }
val betStatusDisplayer = remember { val betStatusDisplayer = remember {
BetStatusBottomSheetBetDisplayer( BetStatusBottomSheetBetDisplayer(
userCoinAmount = currentUser.userCoins, openParticipateSheet = { setParticipateSheetVisibility(true) }
onParticipate = {
mainViewModel.participateToBet(it)
}
) )
} }
@ -152,7 +153,7 @@ fun MainScreen(
navController = navController, navController = navController,
selectBet = { bet, participate -> selectBet = { bet, participate ->
setSelectedBet(bet) setSelectedBet(bet)
betStatusDisplayer.participateBottomSheetVisibility.value = participate setParticipateSheetVisibility(participate)
setStatusVisibility(true) setStatusVisibility(true)
}, },
setLoading = { loading = it }, setLoading = { loading = it },
@ -180,11 +181,14 @@ fun MainScreen(
state = statusBottomSheetState, state = statusBottomSheetState,
sheetVisibility = statusVisibility.value, sheetVisibility = statusVisibility.value,
sheetBackVisibility = sheetBackVisibility.value, sheetBackVisibility = sheetBackVisibility.value,
onDismiss = { onDismiss = { setStatusVisibility(false) },
setStatusVisibility(false)
},
bet = selectedBet, bet = selectedBet,
displayBet = { betStatusDisplayer.DisplayBet(it) } paddingValues = betStatusDisplayer.paddingValues.value,
displayBet = { betStatusDisplayer.DisplayBet(it) },
userCoinAmount = mainViewModel.currentUserState.userCoins,
onParticipate = { mainViewModel.participateToBet(it) },
participateSheetVisibility = participateSheetVisibility,
setParticipateSheetVisibility = setParticipateSheetVisibility
) )
AllInLoading(visible = loading) AllInLoading(visible = loading)
BackHandler( BackHandler(

@ -62,7 +62,7 @@ class MainViewModel @Inject constructor(
viewModelScope.launch { viewModelScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
loading.value = true loading.value = true
currentUserState.userCoins.intValue += 50 currentUserState.userCoins.intValue -= stake
Thread.sleep(1000) Thread.sleep(1000)
loading.value = false loading.value = false
} }

@ -9,7 +9,7 @@ import fr.iut.alldev.allin.data.model.bet.MatchBet
import fr.iut.alldev.allin.data.model.bet.YesNoBet import fr.iut.alldev.allin.data.model.bet.YesNoBet
import java.time.ZonedDateTime import java.time.ZonedDateTime
class BetPreviewProvider: PreviewParameterProvider<Bet> { class BetPreviewProvider : PreviewParameterProvider<Bet> {
override val values = sequenceOf( override val values = sequenceOf(
YesNoBet( YesNoBet(
theme = "Theme", theme = "Theme",
@ -39,7 +39,7 @@ class BetPreviewProvider: PreviewParameterProvider<Bet> {
isPublic = true, isPublic = true,
betStatus = BetStatus.Finished(BetFinishedStatus.WON), betStatus = BetStatus.Finished(BetFinishedStatus.WON),
creator = "creator", creator = "creator",
possibleAnswers = setOf( possibleAnswers = listOf(
"Answer 1", "Answer 1",
"Answer 2", "Answer 2",
"Answer 3", "Answer 3",

@ -40,7 +40,7 @@ class BetWithStatusPreviewProvider : PreviewParameterProvider<Bet> {
isPublic = true, isPublic = true,
betStatus = status, betStatus = status,
creator = "creator", creator = "creator",
possibleAnswers = setOf( possibleAnswers = listOf(
"Answer 1", "Answer 1",
"Answer 2", "Answer 2",
"Answer 3", "Answer 3",

@ -2,7 +2,6 @@ package fr.iut.alldev.allin.ui.register
import androidx.compose.foundation.* import androidx.compose.foundation.*
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -49,7 +48,6 @@ fun RegisterScreen(
val (password, setPassword) = remember { registerViewModel.password } val (password, setPassword) = remember { registerViewModel.password }
val (passwordValidation, setPasswordValidation) = remember { registerViewModel.passwordValidation } val (passwordValidation, setPasswordValidation) = remember { registerViewModel.passwordValidation }
val bringIntoViewRequester = remember { BringIntoViewRequester() }
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
val usernameFieldName = stringResource(id = R.string.username) val usernameFieldName = stringResource(id = R.string.username)
@ -117,46 +115,42 @@ fun RegisterScreen(
verticalArrangement = Arrangement.spacedBy(20.dp) verticalArrangement = Arrangement.spacedBy(20.dp)
) { ) {
AllInTextField( AllInTextField(
modifier = Modifier.fillMaxWidth(),
placeholder = usernameFieldName, placeholder = usernameFieldName,
value = username, value = username,
onValueChange = setUsername, modifier = Modifier.fillMaxWidth(),
maxChar = 20, maxChar = 20,
onValueChange = setUsername,
errorText = usernameError.errorResource(), errorText = usernameError.errorResource(),
bringIntoViewRequester = bringIntoViewRequester,
imeAction = ImeAction.Next, imeAction = ImeAction.Next,
keyboardActions = keyboardActions keyboardActions = keyboardActions
) )
AllInTextField( AllInTextField(
modifier = Modifier.fillMaxWidth(),
placeholder = emailFieldName, placeholder = emailFieldName,
value = email, value = email,
modifier = Modifier.fillMaxWidth(),
onValueChange = setEmail, onValueChange = setEmail,
errorText = emailError.errorResource(), errorText = emailError.errorResource(),
keyboardType = KeyboardType.Email, keyboardType = KeyboardType.Email,
bringIntoViewRequester = bringIntoViewRequester,
imeAction = ImeAction.Next, imeAction = ImeAction.Next,
keyboardActions = keyboardActions keyboardActions = keyboardActions
) )
AllInPasswordField( AllInPasswordField(
modifier = Modifier.fillMaxWidth(),
placeholder = passwordFieldName, placeholder = passwordFieldName,
value = password, value = password,
errorText = passwordError.errorResource(), modifier = Modifier.fillMaxWidth(),
onValueChange = setPassword,
bringIntoViewRequester = bringIntoViewRequester,
imeAction = ImeAction.Next, imeAction = ImeAction.Next,
keyboardActions = keyboardActions keyboardActions = keyboardActions,
errorText = passwordError.errorResource(),
onValueChange = setPassword
) )
AllInPasswordField( AllInPasswordField(
modifier = Modifier.fillMaxWidth(),
placeholder = stringResource(id = R.string.confirm_password), placeholder = stringResource(id = R.string.confirm_password),
value = passwordValidation, value = passwordValidation,
errorText = passwordValidationError.errorResource(), modifier = Modifier.fillMaxWidth(),
onValueChange = setPasswordValidation,
bringIntoViewRequester = bringIntoViewRequester,
imeAction = ImeAction.Done, imeAction = ImeAction.Done,
keyboardActions = keyboardActions keyboardActions = keyboardActions,
errorText = passwordValidationError.errorResource(),
onValueChange = setPasswordValidation
) )
} }
} }

@ -17,11 +17,11 @@ interface BetDisplayer {
} }
@Composable @Composable
fun DisplayYesNoBet(b: YesNoBet) fun DisplayYesNoBet(bet: YesNoBet)
@Composable @Composable
fun DisplayMatchBet(b: MatchBet) fun DisplayMatchBet(bet: MatchBet)
@Composable @Composable
fun DisplayCustomBet(b: CustomBet) fun DisplayCustomBet(bet: CustomBet)
} }

@ -0,0 +1,96 @@
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.RequestUser
import fr.iut.alldev.allin.data.api.model.ResponseBet
import fr.iut.alldev.allin.data.api.model.ResponseUser
import fr.iut.alldev.allin.data.model.bet.NO_VALUE
import fr.iut.alldev.allin.data.model.bet.YES_VALUE
import java.time.ZonedDateTime
import java.util.UUID
class MockAllInApi : AllInApi {
override suspend fun login(body: CheckUser): ResponseUser {
return users.find { it.first.username == body.login && it.second == body.password }?.first
?: throw AllInAPIException("Invalid login/password.")
}
override suspend fun login(token: String): ResponseUser {
return users.find { it.first.token == 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 = body.nbCoins,
token = "${body.username} ${users.size}"
) to body.password
users.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,
createdBy = ""
)
)
}
override suspend fun getAllBets(): List<ResponseBet> = mockBets.toList()
}
private val users = mutableListOf(
ResponseUser(
id = "UUID 1",
username = "User 1",
email = "john@doe.fr",
nbCoins = 250,
token = "token 1"
) to "psswrd"
)
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"
),
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"
),
ResponseBet(
id = "UUID3",
theme = "Sport",
sentenceBet = "Nouveau record du monde ?",
endRegistration = ZonedDateTime.now().plusDays(3),
endBet = ZonedDateTime.now().plusDays(4),
isPrivate = false,
response = listOf(YES_VALUE, NO_VALUE),
createdBy = "Armure"
)
)

@ -4,6 +4,8 @@ import androidx.annotation.Keep
import fr.iut.alldev.allin.data.model.bet.Bet import fr.iut.alldev.allin.data.model.bet.Bet
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.CustomBet
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.YesNoBet import fr.iut.alldev.allin.data.model.bet.YesNoBet
import fr.iut.alldev.allin.data.serialization.SimpleDateSerializer import fr.iut.alldev.allin.data.serialization.SimpleDateSerializer
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -12,7 +14,7 @@ import java.time.ZonedDateTime
@Keep @Keep
@Serializable @Serializable
data class ResponseBet( data class ResponseBet(
val id: Int?, val id: String?,
val theme: String, val theme: String,
val sentenceBet: String, val sentenceBet: String,
@Serializable(SimpleDateSerializer::class) val endRegistration: ZonedDateTime, @Serializable(SimpleDateSerializer::class) val endRegistration: ZonedDateTime,
@ -22,7 +24,7 @@ data class ResponseBet(
val createdBy: String val createdBy: String
) { ) {
fun toBet(): Bet { fun toBet(): Bet {
if (response.toSet() == setOf("Yes", "No")) { if (response.toSet() == setOf(YES_VALUE, NO_VALUE)) {
return YesNoBet( return YesNoBet(
theme = theme, theme = theme,
phrase = sentenceBet, phrase = sentenceBet,
@ -41,7 +43,7 @@ data class ResponseBet(
isPublic = !isPrivate, isPublic = !isPrivate,
betStatus = BetStatus.Waiting, betStatus = BetStatus.Waiting,
creator = createdBy, creator = createdBy,
possibleAnswers = response.toSet() possibleAnswers = response
) )
} }
} }

@ -16,15 +16,16 @@ data class RequestUser(
@Keep @Keep
@Serializable @Serializable
data class ResponseUser( data class ResponseUser(
val id: String,
val username: String, val username: String,
val email: String, val email: String,
var nbCoins: Int, var nbCoins: Int,
var token: String? = null, var token: String? = null,
) { ) {
fun toUser() = User( fun toUser() = User(
id = id,
username = username, username = username,
email = email, email = email,
id = "",
coins = nbCoins coins = nbCoins
) )
} }

@ -5,18 +5,25 @@ import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import fr.iut.alldev.allin.data.api.AllInApi import fr.iut.alldev.allin.data.api.AllInApi
import fr.iut.alldev.allin.data.api.MockAllInApi
import fr.iut.alldev.allin.data.di.NetworkModule.createRetrofit import fr.iut.alldev.allin.data.di.NetworkModule.createRetrofit
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import javax.inject.Singleton import javax.inject.Singleton
const val mock = true
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
class ApiModule { class ApiModule {
@Provides @Provides
@Singleton @Singleton
fun provideAllInApi(@AllInUrl url: HttpUrl, okHttpClient: OkHttpClient): AllInApi { fun provideAllInApi(@AllInUrl url: HttpUrl, okHttpClient: OkHttpClient): AllInApi {
val retrofit = createRetrofit(url = url, okHttpClient = okHttpClient) return if (mock) {
return retrofit.create(AllInApi::class.java) MockAllInApi()
} else {
val retrofit = createRetrofit(url = url, okHttpClient = okHttpClient)
retrofit.create(AllInApi::class.java)
}
} }
} }

@ -4,7 +4,7 @@ import fr.iut.alldev.allin.data.api.model.RequestBet
import java.time.ZonedDateTime import java.time.ZonedDateTime
sealed class Bet( sealed class Bet(
open val id: Int? = null, open val id: String? = null,
open val creator: String, open val creator: String,
open val theme: String, open val theme: String,
open val phrase: String, open val phrase: String,

@ -14,7 +14,7 @@ class BetFactory {
isPublic: Boolean, isPublic: Boolean,
nameTeam1: String = "", nameTeam1: String = "",
nameTeam2: String = "", nameTeam2: String = "",
possibleAnswers: Set<String> = emptySet(), possibleAnswers: List<String> = emptyList(),
): Bet = ): Bet =
when (betType) { when (betType) {

@ -3,7 +3,7 @@ package fr.iut.alldev.allin.data.model.bet
import java.time.ZonedDateTime import java.time.ZonedDateTime
data class CustomBet( data class CustomBet(
override val id: Int? = null, override val id: String? = null,
override val creator: String, override val creator: String,
override val theme: String, override val theme: String,
override val phrase: String, override val phrase: String,
@ -11,7 +11,7 @@ data class CustomBet(
override val endBetDate: ZonedDateTime, override val endBetDate: ZonedDateTime,
override val isPublic: Boolean, override val isPublic: Boolean,
override val betStatus: BetStatus, override val betStatus: BetStatus,
val possibleAnswers: Set<String>, val possibleAnswers: List<String>
) : Bet( ) : Bet(
id, id,
creator, creator,
@ -22,5 +22,5 @@ data class CustomBet(
isPublic, isPublic,
betStatus betStatus
) { ) {
override fun getResponses(): List<String> = possibleAnswers.toList() override fun getResponses(): List<String> = possibleAnswers
} }

@ -3,7 +3,7 @@ package fr.iut.alldev.allin.data.model.bet
import java.time.ZonedDateTime import java.time.ZonedDateTime
data class MatchBet( data class MatchBet(
override val id: Int? = null, override val id: String? = null,
override val creator: String, override val creator: String,
override val theme: String, override val theme: String,
override val phrase: String, override val phrase: String,
@ -12,7 +12,7 @@ data class MatchBet(
override val isPublic: Boolean, override val isPublic: Boolean,
override val betStatus: BetStatus, override val betStatus: BetStatus,
val nameTeam1: String, val nameTeam1: String,
val nameTeam2: String, val nameTeam2: String
) : Bet( ) : Bet(
id, id,
creator, creator,

@ -2,15 +2,18 @@ package fr.iut.alldev.allin.data.model.bet
import java.time.ZonedDateTime import java.time.ZonedDateTime
const val YES_VALUE = "Yes"
const val NO_VALUE = "No"
data class YesNoBet( data class YesNoBet(
override val id: Int? = null, override val id: String? = null,
override val creator: String, override val creator: String,
override val theme: String, override val theme: String,
override val phrase: String, override val phrase: String,
override val endRegisterDate: ZonedDateTime, override val endRegisterDate: ZonedDateTime,
override val endBetDate: ZonedDateTime, override val endBetDate: ZonedDateTime,
override val isPublic: Boolean, override val isPublic: Boolean,
override val betStatus: BetStatus, override val betStatus: BetStatus
) : Bet( ) : Bet(
id, id,
creator, creator,
@ -21,5 +24,5 @@ data class YesNoBet(
isPublic, isPublic,
betStatus betStatus
) { ) {
override fun getResponses(): List<String> = listOf("Yes", "No") override fun getResponses(): List<String> = listOf(YES_VALUE, NO_VALUE)
} }
Loading…
Cancel
Save