Add bet confirmation screen
continuous-integration/drone/push Build is passing Details

pull/4/head
Arthur VALIN 1 year ago
parent 8c073bdec0
commit 58d788f58f

@ -1,49 +1,48 @@
package fr.iut.alldev.allin.test.mock package fr.iut.alldev.allin.test.mock
import fr.iut.alldev.allin.data.model.bet.BetStatus import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
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.YesNoBet
import java.time.ZonedDateTime
object Bets { object Bets {
val bets by lazy { val bets by lazy {
listOf( listOf<BetDetail>(
YesNoBet( /* YesNoBet(
theme = "Theme", theme = "Theme",
phrase = "Phrase", phrase = "Phrase",
endRegisterDate = ZonedDateTime.now(), endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(), endBetDate = ZonedDateTime.now(),
isPublic = true, isPublic = true,
betStatus = BetStatus.InProgress, betStatus = BetStatus.InProgress,
creator = "creator", creator = "creator",
), id = ""
MatchBet( ),
theme = "Theme", MatchBet(
phrase = "Phrase", theme = "Theme",
endRegisterDate = ZonedDateTime.now(), phrase = "Phrase",
endBetDate = ZonedDateTime.now(), endRegisterDate = ZonedDateTime.now(),
isPublic = true, endBetDate = ZonedDateTime.now(),
betStatus = BetStatus.InProgress, isPublic = true,
nameTeam1 = "Team_1", betStatus = BetStatus.InProgress,
nameTeam2 = "Team_2", nameTeam1 = "Team_1",
creator = "creator" nameTeam2 = "Team_2",
), creator = "creator",
CustomBet( id = ""
theme = "Theme", ),
phrase = "Phrase", CustomBet(
endRegisterDate = ZonedDateTime.now(), theme = "Theme",
endBetDate = ZonedDateTime.now(), phrase = "Phrase",
isPublic = true, endRegisterDate = ZonedDateTime.now(),
betStatus = BetStatus.InProgress, endBetDate = ZonedDateTime.now(),
creator = "creator", isPublic = true,
possibleAnswers = listOf( betStatus = BetStatus.InProgress,
"Answer 1", creator = "creator",
"Answer 2", possibleAnswers = listOf(
"Answer 3", "Answer 1",
"Answer 4" "Answer 2",
) "Answer 3",
), "Answer 4"
),
id = ""
)*/
) )
} }
} }

@ -4,25 +4,23 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import fr.iut.alldev.allin.data.model.bet.CustomBet import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
import fr.iut.alldev.allin.data.model.bet.MatchBet
import fr.iut.alldev.allin.data.model.bet.YesNoBet
import fr.iut.alldev.allin.test.TestTags import fr.iut.alldev.allin.test.TestTags
import fr.iut.alldev.allin.vo.bet.BetDisplayer import fr.iut.alldev.allin.vo.bet.BetDisplayer
class BetTestDisplayer : BetDisplayer { class BetTestDisplayer : BetDisplayer {
@Composable @Composable
override fun DisplayYesNoBet(bet: YesNoBet) { override fun DisplayYesNoBet(betDetail: BetDetail) {
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(bet: MatchBet) { override fun DisplayMatchBet(betDetail: BetDetail) {
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(bet: CustomBet) { override fun DisplayCustomBet(betDetail: BetDetail) {
Text("This is a Custom Bet", Modifier.testTag(TestTags.CUSTOM_BET.tag)) Text("This is a Custom Bet", Modifier.testTag(TestTags.CUSTOM_BET.tag))
} }
} }

@ -14,6 +14,9 @@ sealed class FieldErrorState(
) { ) {
data object NoError : FieldErrorState() data object NoError : FieldErrorState()
data object Mandatory : FieldErrorState(R.string.FieldError_Mandatory)
data class TooShort(val fieldName: String, val minChar: Int) : data class TooShort(val fieldName: String, val minChar: Int) :
FieldErrorState(R.string.FieldError_TooShort, fieldName, minChar) FieldErrorState(R.string.FieldError_TooShort, fieldName, minChar)

@ -5,17 +5,13 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import fr.iut.alldev.allin.data.model.bet.Bet import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.data.repository.BetRepository import fr.iut.alldev.allin.data.repository.BetRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@ -23,14 +19,16 @@ class BetViewModel @Inject constructor(
private val betRepository: BetRepository private val betRepository: BetRepository
) : ViewModel() { ) : ViewModel() {
private val _isRefreshing = MutableStateFlow(false) private val _isRefreshing by lazy { MutableStateFlow(false) }
val isRefreshing: StateFlow<Boolean> val isRefreshing: StateFlow<Boolean>
get() = _isRefreshing.asStateFlow() get() = _isRefreshing.asStateFlow()
private val _bets: MutableStateFlow<List<Bet>> by lazy {
MutableStateFlow(emptyList())
}
val bets: StateFlow<List<Bet>> by lazy { val bets: StateFlow<List<Bet>> by lazy {
flow { _bets.asStateFlow()
kotlin.runCatching { emitAll(betRepository.getAllBets()) }
}
.filterNotNull() .filterNotNull()
.stateIn( .stateIn(
viewModelScope, viewModelScope,
@ -39,16 +37,22 @@ class BetViewModel @Inject constructor(
) )
} }
private fun refreshData() { init {
Thread.sleep(1000) viewModelScope.launch {
refreshData()
}
}
private suspend fun refreshData() {
runCatching {
_bets.emit(betRepository.getAllBets())
}
} }
fun refresh() { fun refresh() {
viewModelScope.launch { viewModelScope.launch {
_isRefreshing.emit(true) _isRefreshing.emit(true)
withContext(Dispatchers.IO) { refreshData()
refreshData()
}
_isRefreshing.emit(false) _isRefreshing.emit(false)
} }
} }

@ -0,0 +1,388 @@
package fr.iut.alldev.allin.ui.betConfirmation
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.SheetState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.os.ConfigurationCompat
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.ext.formatToMediumDateNoYear
import fr.iut.alldev.allin.data.ext.formatToTime
import fr.iut.alldev.allin.data.model.bet.BetFinishedStatus
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.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.vo.BetAnswerDetail
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
import fr.iut.alldev.allin.ext.formatToSimple
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInBottomSheet
import fr.iut.alldev.allin.ui.core.AllInButton
import fr.iut.alldev.allin.ui.core.AllInCard
import fr.iut.alldev.allin.ui.core.AllInMarqueeBox
import fr.iut.alldev.allin.ui.core.bet.BetCard
import java.time.ZonedDateTime
import java.util.Locale
@Composable
fun BetConfirmationBottomSheet(
state: SheetState,
sheetVisibility: Boolean,
betDetail: BetDetail,
onDismiss: () -> Unit,
onConfirm: (selectedAnswer: String) -> Unit
) {
AllInBottomSheet(
sheetVisibility = sheetVisibility,
onDismiss = onDismiss,
state = state,
dragHandle = null
) {
BetConfirmationBottomSheetContent(
betDetail = betDetail,
onConfirm = {
onConfirm(it)
onDismiss()
},
onClose = onDismiss
)
}
}
@Composable
fun BetConfirmationBottomSheetAnswer(
text: String,
odds: Float,
modifier: Modifier = Modifier,
color: Color = AllInTheme.colors.allInBlue,
isSelected: Boolean,
locale: Locale,
onClick: () -> Unit
) {
val backColor = if (isSelected) AllInTheme.colors.allInPurple else AllInTheme.colors.white
val contentColor = if (isSelected) AllInTheme.colors.white else null
AllInCard(
backgroundColor = backColor,
onClick = onClick,
modifier = modifier
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 18.dp),
) {
Text(
text = text.uppercase(),
color = contentColor ?: color,
style = AllInTheme.typography.h1,
fontSize = 40.sp,
modifier = Modifier.align(Alignment.Center)
)
AllInCard(
radius = 50.dp,
backgroundColor = contentColor ?: AllInTheme.colors.allInPurple,
modifier = Modifier.align(Alignment.CenterEnd)
) {
Box(Modifier.padding(vertical = 4.dp, horizontal = 8.dp)) {
Text(
text = "x${odds.formatToSimple(locale)}",
color = backColor,
style = AllInTheme.typography.h2
)
}
}
}
}
}
@Composable
fun ConfirmationAnswers(
betDetail: BetDetail,
selectedAnswer: String?,
onClick: (String) -> Unit
) {
val configuration = LocalConfiguration.current
val locale = remember { ConfigurationCompat.getLocales(configuration).get(0) ?: Locale.getDefault() }
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
when (betDetail.bet) {
is CustomBet -> items((betDetail.bet as CustomBet).possibleAnswers) {
betDetail.getAnswerOfResponse(it)?.let {
val opacity by animateFloatAsState(targetValue = if (selectedAnswer != null && selectedAnswer != it.response) .5f else 1f, label = "")
BetConfirmationBottomSheetAnswer(
text = it.response,
odds = it.odds,
locale = locale,
onClick = { onClick(it.response) },
isSelected = selectedAnswer == it.response,
modifier = Modifier.alpha(opacity)
)
}
}
is MatchBet -> {
val bet = (betDetail.bet as MatchBet)
item {
betDetail.getAnswerOfResponse(bet.nameTeam1)?.let {
val opacity by animateFloatAsState(targetValue = if (selectedAnswer != null && selectedAnswer != it.response) .5f else 1f, label = "")
BetConfirmationBottomSheetAnswer(
text = it.response,
odds = it.odds,
locale = locale,
onClick = { onClick(it.response) },
isSelected = selectedAnswer == it.response,
modifier = Modifier.alpha(opacity)
)
}
}
item {
betDetail.getAnswerOfResponse(bet.nameTeam2)?.let {
val opacity by animateFloatAsState(targetValue = if (selectedAnswer != null && selectedAnswer != it.response) .5f else 1f, label = "")
BetConfirmationBottomSheetAnswer(
text = it.response,
color = AllInTheme.colors.allInBarPink,
odds = it.odds,
locale = locale,
onClick = { onClick(it.response) },
isSelected = selectedAnswer == it.response,
modifier = Modifier
.alpha(opacity)
)
}
}
}
is YesNoBet -> {
item {
betDetail.getAnswerOfResponse(YES_VALUE)?.let {
val opacity by animateFloatAsState(targetValue = if (selectedAnswer != null && selectedAnswer != it.response) .5f else 1f, label = "")
val scale by animateFloatAsState(
targetValue = if (selectedAnswer == null) 1f
else if (selectedAnswer != it.response) .95f else 1.05f,
label = ""
)
BetConfirmationBottomSheetAnswer(
text = it.response,
odds = it.odds,
locale = locale,
onClick = { onClick(it.response) },
isSelected = selectedAnswer == it.response,
modifier = Modifier
.alpha(opacity)
.scale(scale)
)
}
}
item {
betDetail.getAnswerOfResponse(NO_VALUE)?.let {
val opacity by animateFloatAsState(targetValue = if (selectedAnswer != null && selectedAnswer != it.response) .5f else 1f, label = "")
val scale by animateFloatAsState(
targetValue = if (selectedAnswer == null) 1f
else if (selectedAnswer != it.response) .95f else 1.05f,
label = ""
)
BetConfirmationBottomSheetAnswer(
text = it.response,
color = AllInTheme.colors.allInBarPink,
odds = it.odds,
locale = locale,
onClick = { onClick(it.response) },
isSelected = selectedAnswer == it.response,
modifier = Modifier
.alpha(opacity)
.scale(scale)
)
}
}
}
}
}
}
@Composable
fun BetConfirmationBottomSheetContent(
betDetail: BetDetail,
onConfirm: (String) -> Unit,
onClose: () -> Unit
) {
var selectedAnswer by remember { mutableStateOf<String?>(null) }
AllInMarqueeBox(backgroundColor = AllInTheme.colors.allInDarkGrey300) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
IconButton(
onClick = onClose,
modifier = Modifier
.size(24.dp)
.align(Alignment.TopStart)
) {
Icon(
imageVector = Icons.Default.Close,
tint = AllInTheme.colors.white,
contentDescription = null,
modifier = Modifier.size(24.dp)
)
}
Icon(
painter = painterResource(R.drawable.allin),
contentDescription = null,
tint = AllInTheme.colors.white,
modifier = Modifier
.size(40.dp)
.align(Alignment.TopCenter)
)
Column(
modifier = Modifier.align(Alignment.Center),
verticalArrangement = Arrangement.spacedBy(22.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
BetCard(
title = betDetail.bet.phrase,
creator = betDetail.bet.creator,
category = betDetail.bet.theme,
date = betDetail.bet.endBetDate.formatToMediumDateNoYear(),
time = betDetail.bet.endBetDate.formatToTime(),
status = betDetail.bet.betStatus
) {
Row(
modifier = Modifier
.fillMaxWidth()
.background(AllInTheme.colors.allInMainGradient)
.padding(16.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = stringResource(id = R.string.Finished),
color = AllInTheme.colors.white,
style = AllInTheme.typography.h1,
fontSize = 24.sp
)
}
}
Text(
text = "Ce bet est arrivé à la date de fin. Vous devez à présent distribuer les gains en validant le pari gagnant.",
color = AllInTheme.colors.allInLightGrey200,
style = AllInTheme.typography.p2,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Veuillez choisir la réponse finale :",
fontSize = 20.sp,
color = AllInTheme.colors.white,
style = AllInTheme.typography.h1,
modifier = Modifier.fillMaxWidth()
)
ConfirmationAnswers(
betDetail = betDetail,
selectedAnswer = selectedAnswer
) { selectedAnswer = if (selectedAnswer == it) null else it }
}
if (selectedAnswer != null) {
AllInButton(
color = AllInTheme.colors.allInPurple,
text = stringResource(id = R.string.Validate),
textColor = AllInTheme.colors.white,
radius = 5.dp,
onClick = { selectedAnswer?.let(onConfirm) },
modifier = Modifier.align(Alignment.BottomCenter)
)
}
}
}
}
@Preview
@Composable
private fun BetConfirmationBottomSheetContentPreview() {
AllInTheme {
BetConfirmationBottomSheetContent(
betDetail = BetDetail(
bet = YesNoBet(
id = "1",
theme = "Theme",
phrase = "Phrase",
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = true,
betStatus = BetStatus.Finished(BetFinishedStatus.WON),
creator = "creator",
),
answers = 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 = emptyList(),
userParticipation = null
),
onConfirm = { }
) {
}
}
}

@ -64,8 +64,6 @@ fun BetCreationScreen(
val themeFieldName = stringResource(id = R.string.Theme) val themeFieldName = stringResource(id = R.string.Theme)
val phraseFieldName = stringResource(id = R.string.Bet_Phrase) val phraseFieldName = stringResource(id = R.string.Bet_Phrase)
val registerDateFieldName = stringResource(id = R.string.End_registration_date)
val betDateFieldName = stringResource(id = R.string.End_bet_date)
LaunchedEffect(key1 = betTypes) { LaunchedEffect(key1 = betTypes) {
selectionElements = betTypes.map { selectionElements = betTypes.map {
@ -152,8 +150,6 @@ fun BetCreationScreen(
viewModel.createBet( viewModel.createBet(
themeFieldName = themeFieldName, themeFieldName = themeFieldName,
phraseFieldName = phraseFieldName, phraseFieldName = phraseFieldName,
registerDateFieldName = registerDateFieldName,
betDateFieldName = betDateFieldName,
setLoading = setLoading, setLoading = setLoading,
onError = { hasError = true }, onError = { hasError = true },
onSuccess = { onSuccess = {

@ -17,9 +17,6 @@ import timber.log.Timber
import java.time.ZonedDateTime import java.time.ZonedDateTime
import javax.inject.Inject import javax.inject.Inject
const val THEME_MIN_SIZE = 3
const val PHRASE_MIN_SIZE = 5
@HiltViewModel @HiltViewModel
class BetCreationViewModel @Inject constructor( class BetCreationViewModel @Inject constructor(
@AllInCurrentUser val currentUser: User, @AllInCurrentUser val currentUser: User,
@ -27,7 +24,7 @@ class BetCreationViewModel @Inject constructor(
private val keystoreManager: AllInKeystoreManager private val keystoreManager: AllInKeystoreManager
) : ViewModel() { ) : ViewModel() {
var hasError = mutableStateOf(false) private var hasError = mutableStateOf(false)
var theme = mutableStateOf("") var theme = mutableStateOf("")
var phrase = mutableStateOf("") var phrase = mutableStateOf("")
val registerDate = mutableStateOf(ZonedDateTime.now()) val registerDate = mutableStateOf(ZonedDateTime.now())
@ -49,20 +46,16 @@ class BetCreationViewModel @Inject constructor(
} }
private fun verifyField( private fun verifyField(
themeFieldName: String,
phraseFieldName: String,
registerDateFieldName: String, registerDateFieldName: String,
betDateFieldName: String, betDateFieldName: String,
) { ) {
if (theme.value.length < THEME_MIN_SIZE) { if (theme.value.isBlank()) {
themeError.value = themeError.value = FieldErrorState.Mandatory
FieldErrorState.TooShort(themeFieldName.lowercase(), THEME_MIN_SIZE)
hasError.value = true hasError.value = true
} }
if (phrase.value.length < PHRASE_MIN_SIZE) { if (phrase.value.isBlank()) {
phraseError.value = phraseError.value = FieldErrorState.Mandatory
FieldErrorState.TooShort(phraseFieldName.lowercase(), PHRASE_MIN_SIZE)
hasError.value = true hasError.value = true
} }
@ -90,8 +83,6 @@ class BetCreationViewModel @Inject constructor(
fun createBet( fun createBet(
themeFieldName: String, themeFieldName: String,
phraseFieldName: String, phraseFieldName: String,
registerDateFieldName: String,
betDateFieldName: String,
onError: () -> Unit, onError: () -> Unit,
setLoading: (Boolean) -> Unit, setLoading: (Boolean) -> Unit,
onSuccess: () -> Unit onSuccess: () -> Unit
@ -103,8 +94,6 @@ class BetCreationViewModel @Inject constructor(
verifyField( verifyField(
themeFieldName, themeFieldName,
phraseFieldName, phraseFieldName,
registerDateFieldName,
betDateFieldName,
) )
if (!hasError.value) { if (!hasError.value) {

@ -1,13 +1,9 @@
package fr.iut.alldev.allin.ui.betResult package fr.iut.alldev.allin.ui.betResult
import androidx.compose.foundation.MarqueeSpacing
import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@ -18,8 +14,6 @@ import androidx.compose.material3.SheetState
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.rotate
import androidx.compose.ui.draw.scale
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
@ -34,6 +28,7 @@ import fr.iut.alldev.allin.ui.betResult.components.BetResultBottomSheetBetCard
import fr.iut.alldev.allin.ui.betResult.components.BetResultBottomSheetContentCoinAmount import fr.iut.alldev.allin.ui.betResult.components.BetResultBottomSheetContentCoinAmount
import fr.iut.alldev.allin.ui.betResult.components.BetResultBottomSheetContentCongratulations import fr.iut.alldev.allin.ui.betResult.components.BetResultBottomSheetContentCongratulations
import fr.iut.alldev.allin.ui.core.AllInBottomSheet import fr.iut.alldev.allin.ui.core.AllInBottomSheet
import fr.iut.alldev.allin.ui.core.AllInMarqueeBox
import java.time.ZonedDateTime import java.time.ZonedDateTime
@Composable @Composable
@ -76,22 +71,7 @@ fun BetResultBottomSheetContent(
odds: Float, odds: Float,
onClose: () -> Unit onClose: () -> Unit
) { ) {
Box( AllInMarqueeBox(backgroundBrush = AllInTheme.colors.allInMainGradientReverse) {
modifier = Modifier
.fillMaxSize()
.background(AllInTheme.colors.allInMainGradientReverse),
) {
Icon(
painter = painterResource(id = R.drawable.allin_marquee),
contentDescription = null,
modifier = Modifier
.fillMaxSize()
.rotate(11f)
.scale(1.2f)
.offset(x = (-24).dp)
.basicMarquee(spacing = MarqueeSpacing(0.dp)),
tint = AllInTheme.colors.white.copy(alpha = .05f)
)
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@ -146,7 +126,6 @@ fun BetResultBottomSheetContent(
} }
@Preview @Preview
@Preview(widthDp = 800, heightDp = 1280)
@Composable @Composable
private fun BetResultBottomSheetContentPreview() { private fun BetResultBottomSheetContentPreview() {
AllInTheme { AllInTheme {

@ -7,42 +7,11 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInTextIcon 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
fun YesNoDetailsLine(
icon: ImageVector,
yesText: String,
noText: String,
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
AllInTextIcon(
text = yesText,
color = AllInTheme.colors.allInBlue,
icon = rememberVectorPainter(image = icon),
position = IconPosition.LEADING,
size = 10,
iconSize = 15
)
AllInTextIcon(
text = noText,
color = AllInTheme.colors.allInBarPink,
icon = rememberVectorPainter(image = icon),
position = IconPosition.TRAILING,
size = 10,
iconSize = 15
)
}
}
@Composable @Composable
fun YesNoDetailsLine( fun YesNoDetailsLine(
icon: Painter, icon: Painter,
@ -58,7 +27,7 @@ fun YesNoDetailsLine(
color = AllInTheme.colors.allInBlue, color = AllInTheme.colors.allInBlue,
icon = icon, icon = icon,
position = IconPosition.LEADING, position = IconPosition.LEADING,
size = 10, size = 15,
iconSize = 15 iconSize = 15
) )
AllInTextIcon( AllInTextIcon(
@ -66,7 +35,7 @@ fun YesNoDetailsLine(
color = AllInTheme.colors.allInBarPink, color = AllInTheme.colors.allInBarPink,
icon = icon, icon = icon,
position = IconPosition.TRAILING, position = IconPosition.TRAILING,
size = 10, size = 15,
iconSize = 15 iconSize = 15
) )
} }

@ -15,6 +15,7 @@ import androidx.compose.material3.Text
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
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
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
@ -131,17 +132,17 @@ class BetStatusBottomSheetBetDisplayer(
noText = response2Answer?.totalStakes?.toString() ?: "0" noText = response2Answer?.totalStakes?.toString() ?: "0"
) )
YesNoDetailsLine( YesNoDetailsLine(
icon = Icons.Filled.People, icon = rememberVectorPainter(image = Icons.Filled.People),
yesText = response1Answer?.totalParticipants?.toString() ?: "0", yesText = response1Answer?.totalParticipants?.toString() ?: "0",
noText = response2Answer?.totalParticipants?.toString() ?: "0" noText = response2Answer?.totalParticipants?.toString() ?: "0"
) )
YesNoDetailsLine( YesNoDetailsLine(
icon = Icons.Filled.WorkspacePremium, icon = rememberVectorPainter(image = Icons.Filled.WorkspacePremium),
yesText = response1Answer?.highestStake?.toString() ?: "0", yesText = response1Answer?.highestStake?.toString() ?: "0",
noText = response2Answer?.highestStake?.toString() ?: "0" noText = response2Answer?.highestStake?.toString() ?: "0"
) )
YesNoDetailsLine( YesNoDetailsLine(
icon = Icons.Filled.EmojiEvents, icon = rememberVectorPainter(image = Icons.Filled.EmojiEvents),
yesText = "x${response1Answer?.odds?.formatToSimple(locale) ?: "1.00"}", yesText = "x${response1Answer?.odds?.formatToSimple(locale) ?: "1.00"}",
noText = "x${response2Answer?.odds?.formatToSimple(locale) ?: "1.00"}" noText = "x${response2Answer?.odds?.formatToSimple(locale) ?: "1.00"}"
) )

@ -0,0 +1,65 @@
package fr.iut.alldev.allin.ui.core
import androidx.compose.foundation.MarqueeSpacing
import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.theme.AllInTheme
@Composable
fun AllInMarqueeBox(
backgroundColor: Color = AllInTheme.themeColors.mainSurface,
backgroundBrush: Brush? = null,
content: @Composable BoxScope.() -> Unit
) {
Box(
modifier = Modifier
.fillMaxSize().let { itModifier ->
backgroundBrush?.let {
itModifier.background(it)
} ?: itModifier.background(backgroundColor)
}
) {
Icon(
painter = painterResource(id = R.drawable.allin_marquee),
contentDescription = null,
modifier = Modifier
.fillMaxHeight()
.aspectRatio(1f, true)
.scale(1.2f)
.rotate(11f)
.basicMarquee(spacing = MarqueeSpacing(0.dp)),
tint = AllInTheme.colors.white.copy(alpha = .05f)
)
content()
}
}
@Preview
@Preview(widthDp = 800, heightDp = 1280)
@Composable
private fun AllInMarqueeBoxPreview() {
AllInTheme {
AllInMarqueeBox(
backgroundBrush = AllInTheme.colors.allInMainGradient,
) {
Text("CONTENT")
}
}
}

@ -11,6 +11,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.betConfirmation.BetConfirmationBottomSheet
import fr.iut.alldev.allin.ui.betResult.BetResultBottomSheet import fr.iut.alldev.allin.ui.betResult.BetResultBottomSheet
import fr.iut.alldev.allin.ui.betStatus.BetStatusBottomSheet import fr.iut.alldev.allin.ui.betStatus.BetStatusBottomSheet
import fr.iut.alldev.allin.ui.betStatus.vo.BetStatusBottomSheetBetDisplayer import fr.iut.alldev.allin.ui.betStatus.vo.BetStatusBottomSheetBetDisplayer
@ -68,11 +69,10 @@ fun MainScreen(
var loading by remember { mainViewModel.loading } var loading by remember { mainViewModel.loading }
val currentUser = remember { mainViewModel.currentUserState } val currentUser = remember { mainViewModel.currentUserState }
val selectedBet by remember { mainViewModel.selectedBet } val selectedBet by remember { mainViewModel.selectedBet }
val (wonBet, setWonBet) = remember { mainViewModel.wonBet } val wonBet by remember { mainViewModel.wonBet }
val toConfirm by remember { mainViewModel.toConfirmBet }
val (statusVisibility, sheetBackVisibility, setStatusVisibility) = rememberBetStatusVisibilities() val (statusVisibility, sheetBackVisibility, setStatusVisibility) = rememberBetStatusVisibilities()
val (participateSheetVisibility, setParticipateSheetVisibility) = remember { val (participateSheetVisibility, setParticipateSheetVisibility) = remember { mutableStateOf(false) }
mutableStateOf(false)
}
val (displayResult, setDisplayResult) = remember { mutableStateOf(true) } val (displayResult, setDisplayResult) = remember { mutableStateOf(true) }
@ -168,7 +168,7 @@ fun MainScreen(
state = resultBottomSheetState, state = resultBottomSheetState,
sheetVisibility = displayResult, sheetVisibility = displayResult,
onDismiss = { setDisplayResult(false) }, onDismiss = { setDisplayResult(false) },
bet = wonBet, bet = it,
username = currentUser.user.username, username = currentUser.user.username,
coinAmount = 1630, coinAmount = 1630,
stake = 1630, stake = 1630,
@ -177,6 +177,15 @@ fun MainScreen(
) )
} }
toConfirm?.let {
BetConfirmationBottomSheet(
state = resultBottomSheetState,
sheetVisibility = displayResult,
betDetail = it,
onDismiss = { setDisplayResult(false) }
) { /*TODO*/ }
}
BetStatusBottomSheet( BetStatusBottomSheet(
state = statusBottomSheetState, state = statusBottomSheetState,
sheetVisibility = statusVisibility.value, sheetVisibility = statusVisibility.value,

@ -8,7 +8,13 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import fr.iut.alldev.allin.data.model.User import fr.iut.alldev.allin.data.model.User
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.BetFinishedStatus
import fr.iut.alldev.allin.data.model.bet.BetStatus
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.YesNoBet
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
import fr.iut.alldev.allin.data.repository.BetRepository import fr.iut.alldev.allin.data.repository.BetRepository
import fr.iut.alldev.allin.di.AllInCurrentUser import fr.iut.alldev.allin.di.AllInCurrentUser
@ -17,6 +23,7 @@ import fr.iut.alldev.allin.ui.core.snackbar.SnackbarType
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.time.ZonedDateTime
import javax.inject.Inject import javax.inject.Inject
class UserState(val user: User) { class UserState(val user: User) {
@ -48,6 +55,39 @@ class MainViewModel @Inject constructor(
)*/ )*/
) )
val toConfirmBet = mutableStateOf<BetDetail?>(
BetDetail(
bet = YesNoBet(
id = "1",
theme = "Theme",
phrase = "Phrase",
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = true,
betStatus = BetStatus.Finished(BetFinishedStatus.WON),
creator = "creator",
),
answers = 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 = emptyList(),
userParticipation = null
)
)
val snackbarContent: MutableState<SnackbarContent?> by lazy { mutableStateOf(null) } val snackbarContent: MutableState<SnackbarContent?> by lazy { mutableStateOf(null) }
fun putSnackbarContent(content: SnackbarContent) { fun putSnackbarContent(content: SnackbarContent) {
snackbarContent.value = content snackbarContent.value = content

@ -25,7 +25,7 @@ class BetDetailPreviewProvider : PreviewParameterProvider<BetDetail> {
totalParticipants = 1, totalParticipants = 1,
highestStake = 150, highestStake = 150,
odds = 2.0f odds = 2.0f
), )
), ),
participations = listOf( participations = listOf(
Participation( Participation(

@ -14,6 +14,7 @@
<string name="Validate">Valider</string> <string name="Validate">Valider</string>
<string name="Cancel">Annuler</string> <string name="Cancel">Annuler</string>
<string name="Ok">OK</string> <string name="Ok">OK</string>
<string name="FieldError_Mandatory">Ce champ est obligatoire.</string>
<string name="FieldError_TooShort">Le %s doit contenir au moins %d caractères.</string> <string name="FieldError_TooShort">Le %s doit contenir au moins %d caractères.</string>
<string name="FieldError_BadFormat">Le %s a un mauvais format : %s.</string> <string name="FieldError_BadFormat">Le %s a un mauvais format : %s.</string>
<string name="FieldError_NotIdentical">Les champs ne sont pas identiques.</string> <string name="FieldError_NotIdentical">Les champs ne sont pas identiques.</string>

@ -16,6 +16,7 @@
<string name="Validate">Validate</string> <string name="Validate">Validate</string>
<string name="Cancel">Cancel</string> <string name="Cancel">Cancel</string>
<string name="Ok">OK</string> <string name="Ok">OK</string>
<string name="FieldError_Mandatory">This field is mandatory.</string>
<string name="FieldError_TooShort">The %s must contain at least %d characters.</string> <string name="FieldError_TooShort">The %s must contain at least %d characters.</string>
<string name="FieldError_BadFormat">The %s has bad format : %s.</string> <string name="FieldError_BadFormat">The %s has bad format : %s.</string>
<string name="FieldError_NotIdentical">The fields are not identical.</string> <string name="FieldError_NotIdentical">The fields are not identical.</string>

@ -11,5 +11,5 @@ abstract class BetRepository {
abstract suspend fun getCurrentBets(): Flow<List<Bet>> abstract suspend fun getCurrentBets(): Flow<List<Bet>>
abstract suspend fun getBet(id: String, token: String): BetDetail abstract suspend fun getBet(id: String, token: String): BetDetail
abstract suspend fun participateToBet(participation: Participation, token: String) abstract suspend fun participateToBet(participation: Participation, token: String)
abstract suspend fun getAllBets(): Flow<List<Bet>> abstract suspend fun getAllBets(): List<Bet>
} }

@ -110,10 +110,8 @@ class BetRepositoryImpl @Inject constructor(
api.participateToBet(token = token.formatBearerToken(), body = participation.toRequestParticipation()) api.participateToBet(token = token.formatBearerToken(), body = participation.toRequestParticipation())
} }
override suspend fun getAllBets(): Flow<List<Bet>> { override suspend fun getAllBets(): List<Bet> {
return flowOf( return api.getAllBets().map { it.toBet() }
api.getAllBets().map { it.toBet() }
)
} }
} }
Loading…
Cancel
Save