Merge pull request 'Sprint_4' (#4) from Sprint_4 into master
continuous-integration/drone/push Build is passing Details

Reviewed-on: #4
master V4
Arthur VALIN 1 year ago
commit b40788676f

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

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

@ -6,24 +6,24 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.model.bet.BetFinishedStatus.LOST
import fr.iut.alldev.allin.data.model.bet.BetFinishedStatus.WON
import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.theme.AllInTheme
@StringRes
fun BetStatus.getTitleId(): Int {
return when (this) {
is BetStatus.Finished -> R.string.bet_status_finished
BetStatus.InProgress -> R.string.bet_status_in_progress
BetStatus.Waiting -> R.string.bet_status_waiting
BetStatus.IN_PROGRESS -> R.string.bet_status_in_progress
BetStatus.WAITING -> R.string.bet_status_waiting
BetStatus.CLOSING -> R.string.bet_status_closing
BetStatus.FINISHED -> R.string.bet_status_finished
BetStatus.CANCELLED -> R.string.bet_status_cancelled
}
}
@StringRes
fun BetStatus.getDateStartLabelId(): Int {
return when (this) {
is BetStatus.Finished -> R.string.Started
BetStatus.CLOSING, BetStatus.FINISHED, BetStatus.CANCELLED -> R.string.Started
else -> R.string.Starting
}
}
@ -31,7 +31,7 @@ fun BetStatus.getDateStartLabelId(): Int {
@StringRes
fun BetStatus.getDateEndLabelId(): Int {
return when (this) {
is BetStatus.Finished -> R.string.Ended
BetStatus.CLOSING, BetStatus.FINISHED, BetStatus.CANCELLED -> R.string.Ended
else -> R.string.Ends
}
}
@ -39,40 +39,38 @@ fun BetStatus.getDateEndLabelId(): Int {
@Composable
fun BetStatus.getColor(): Color {
return when (this) {
is BetStatus.Finished -> AllInTheme.colors.allInBetFinish
BetStatus.InProgress -> AllInTheme.colors.allInBetInProgress
BetStatus.Waiting -> AllInTheme.colors.allInBetWaiting
BetStatus.FINISHED -> AllInTheme.colors.allInBetFinish
BetStatus.IN_PROGRESS -> AllInTheme.colors.allInBetInProgress
BetStatus.WAITING -> AllInTheme.colors.allInBetWaiting
else -> AllInTheme.colors.allInBetFinish // TODO
}
}
@Composable
fun BetStatus.getTextColor(): Color {
return when (this) {
is BetStatus.Finished -> AllInTheme.colors.allInBetFinishText
BetStatus.InProgress -> AllInTheme.colors.allInBetInProgressText
BetStatus.Waiting -> AllInTheme.colors.allInBetWaitingText
BetStatus.FINISHED -> AllInTheme.colors.allInBetFinishText
BetStatus.IN_PROGRESS -> AllInTheme.colors.allInBetInProgressText
BetStatus.WAITING -> AllInTheme.colors.allInBetWaitingText
else -> AllInTheme.colors.allInBetFinishText // TODO
}
}
@StringRes
fun BetStatus.getBetHistoryPhrase(): Int {
fun BetStatus.getBetHistoryPhrase(won: Boolean): Int {
return when (this) {
is BetStatus.Finished -> when (this.status) {
WON -> R.string.bet_history_status_won
LOST -> R.string.bet_history_status_lost
}
BetStatus.FINISHED ->
if (won) R.string.bet_history_status_won else R.string.bet_history_status_lost
else -> R.string.bet_history_status_in_progress
}
}
@Composable
fun BetStatus.getBetHistoryStatusColor(): Brush {
fun BetStatus.getBetHistoryStatusColor(won: Boolean): Brush {
return when (this) {
is BetStatus.Finished -> when (this.status) {
WON -> AllInTheme.colors.allInMainGradient
LOST -> AllInTheme.colors.allInDarkGradient
}
BetStatus.FINISHED ->
if (won) AllInTheme.colors.allInMainGradient else AllInTheme.colors.allInDarkGradient
else -> SolidColor(AllInTheme.colors.allInDarkGrey100)
}

@ -12,7 +12,7 @@ import fr.iut.alldev.allin.data.model.bet.BetType
@StringRes
fun BetType.getTitleId(): Int {
return when (this) {
BetType.YES_NO -> R.string.yes_no
BetType.BINARY -> R.string.yes_no
BetType.MATCH -> R.string.sport_match
BetType.CUSTOM -> R.string.custom_answers
}
@ -20,7 +20,7 @@ fun BetType.getTitleId(): Int {
fun BetType.getIcon(): ImageVector {
return when (this) {
BetType.YES_NO -> Icons.AutoMirrored.Default.HelpOutline
BetType.BINARY -> Icons.AutoMirrored.Default.HelpOutline
BetType.MATCH -> Icons.Default.SportsSoccer
BetType.CUSTOM -> Icons.Default.Edit
}

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

@ -8,4 +8,5 @@ abstract class AllInKeystoreManager {
abstract fun putToken(token: String)
abstract fun getToken(): String?
abstract fun deleteToken()
fun getTokenOrEmpty() = getToken() ?: ""
}

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

@ -0,0 +1,403 @@
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.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 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)
.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,
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 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) {
selectionElements = betTypes.map {
@ -152,8 +150,6 @@ fun BetCreationScreen(
viewModel.createBet(
themeFieldName = themeFieldName,
phraseFieldName = phraseFieldName,
registerDateFieldName = registerDateFieldName,
betDateFieldName = betDateFieldName,
setLoading = setLoading,
onError = { hasError = true },
onSuccess = {

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

@ -43,7 +43,7 @@ fun BetCreationScreenAnswerTab(
)
Spacer(modifier = Modifier.height(26.dp))
when (selectedBetType) {
BetType.YES_NO -> {
BetType.BINARY -> {
Column(
modifier = Modifier.padding(vertical = 20.dp),
verticalArrangement = Arrangement.spacedBy(17.dp)

@ -0,0 +1,30 @@
package fr.iut.alldev.allin.ui.betHistory
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
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.ui.betHistory.components.GenericHistory
@Composable
fun BetCurrentScreen(
viewModel: BetCurrentViewModel = hiltViewModel()
) {
val bets by viewModel.bets.collectAsState()
GenericHistory(
title = stringResource(id = R.string.bet_history_current_title),
bets = bets,
getTitle = { it.bet.phrase },
getCreator = { it.bet.creator },
getCategory = { it.bet.theme },
getEndRegisterDate = { it.bet.endRegisterDate.formatToMediumDateNoYear() },
getEndBetTime = { it.bet.endBetDate.formatToTime() },
getStatus = { it.bet.betStatus },
getNbCoins = { it.userParticipation?.stake ?: 0 },
getWon = { true }
)
}

@ -0,0 +1,44 @@
package fr.iut.alldev.allin.ui.betHistory
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
import fr.iut.alldev.allin.data.repository.BetRepository
import fr.iut.alldev.allin.keystore.AllInKeystoreManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class BetCurrentViewModel @Inject constructor(
private val betRepository: BetRepository,
private val keystoreManager: AllInKeystoreManager
) : ViewModel() {
private val _bets: MutableStateFlow<List<BetDetail>> by lazy {
MutableStateFlow(emptyList())
}
val bets: StateFlow<List<BetDetail>> by lazy {
_bets.asStateFlow()
.filterNotNull()
.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5_000L),
emptyList()
)
}
init {
viewModelScope.launch {
_bets.emit(
betRepository.getToConfirm(keystoreManager.getTokenOrEmpty())
)
}
}
}

@ -1,65 +1,30 @@
package fr.iut.alldev.allin.ui.betHistory
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
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.theme.AllInTheme
import fr.iut.alldev.allin.ui.betHistory.components.BetHistoryScreenCard
import fr.iut.alldev.allin.ui.betHistory.components.GenericHistory
@Composable
fun BetHistoryScreen(
isCurrent: Boolean,
viewModel: BetHistoryViewModel = hiltViewModel(),
viewModel: BetHistoryViewModel = hiltViewModel()
) {
val bets by viewModel.bets.collectAsState()
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 18.dp),
verticalArrangement = Arrangement.spacedBy(18.dp),
) {
item {
Text(
text = stringResource(
id = if (isCurrent) R.string.bet_history_current_title
else R.string.bet_history_title
),
style = AllInTheme.typography.h1,
color = AllInTheme.colors.allInGrey,
fontSize = 24.sp,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
bets?.let { bets ->
items(bets) {
BetHistoryScreenCard(
title = it.phrase,
creator = it.creator,
category = it.theme,
date = it.endRegisterDate.formatToMediumDateNoYear(),
time = it.endRegisterDate.formatToTime(),
status = it.betStatus,
nbCoins = 230
)
}
}
}
GenericHistory(
title = stringResource(id = R.string.bet_history_title),
bets = bets,
getTitle = { it.bet.phrase },
getCreator = { it.bet.creator },
getCategory = { it.bet.theme },
getEndRegisterDate = { it.bet.endRegisterDate.formatToMediumDateNoYear() },
getEndBetTime = { it.bet.endBetDate.formatToTime() },
getStatus = { it.bet.betStatus },
getNbCoins = { it.amount },
getWon = { it.won }
)
}

@ -1,35 +1,44 @@
package fr.iut.alldev.allin.ui.betHistory
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.data.model.bet.BetResultDetail
import fr.iut.alldev.allin.data.repository.BetRepository
import fr.iut.alldev.allin.ui.navigation.NavArguments
import fr.iut.alldev.allin.keystore.AllInKeystoreManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class BetHistoryViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val betRepository: BetRepository,
private val keystoreManager: AllInKeystoreManager
) : ViewModel() {
private val isCurrent: Boolean? = savedStateHandle[NavArguments.ARG_BET_HISTORY_IS_CURRENT]
private val _bets: MutableStateFlow<List<BetResultDetail>> by lazy {
MutableStateFlow(emptyList())
}
val bets: StateFlow<List<BetResultDetail>> by lazy {
_bets.asStateFlow()
.filterNotNull()
.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5_000L),
emptyList()
)
}
val bets: StateFlow<List<Bet>?> by lazy {
flowOf(isCurrent).filterNotNull().flatMapConcat {
if (it) betRepository.getCurrentBets()
else betRepository.getHistory()
}.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5_000L),
null
)
init {
viewModelScope.launch {
_bets.emit(
betRepository.getHistory(keystoreManager.getTokenOrEmpty())
)
}
}
}

@ -51,14 +51,15 @@ val betHistoryStatusInlineContent = mapOf(
@Composable
fun BetHistoryBetStatus(
status: BetStatus,
won: Boolean,
nbCoins: Int,
) {
val betHistoryPhrase = stringResource(id = status.getBetHistoryPhrase(), nbCoins)
val betHistoryPhrase = stringResource(id = status.getBetHistoryPhrase(won), nbCoins)
Row(
modifier = Modifier
.fillMaxWidth()
.background(status.getBetHistoryStatusColor())
.background(status.getBetHistoryStatusColor(won))
.padding(16.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
@ -85,7 +86,8 @@ private fun BetHistoryBetStatusPreview(
AllInTheme {
BetHistoryBetStatus(
status = betStatus,
nbCoins = 230
nbCoins = 230,
won = true
)
}
}

@ -20,6 +20,7 @@ fun BetHistoryScreenCard(
time: String,
status: BetStatus,
nbCoins: Int,
won: Boolean
) {
BetCard(
title = title,
@ -32,7 +33,8 @@ fun BetHistoryScreenCard(
) {
BetHistoryBetStatus(
status = status,
nbCoins = nbCoins
nbCoins = nbCoins,
won = won
)
}
}
@ -51,7 +53,8 @@ private fun BetHistoryScreenCardPreview(
date = "Date",
time = "Time",
status = betStatus,
nbCoins = 123
nbCoins = 123,
won = true
)
}
}

@ -0,0 +1,60 @@
package fr.iut.alldev.allin.ui.betHistory.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.theme.AllInTheme
@Composable
fun <T> GenericHistory(
title: String,
bets: List<T>,
getTitle: (T) -> String,
getCreator: (T) -> String,
getCategory: (T) -> String,
getEndRegisterDate: (T) -> String,
getEndBetTime: (T) -> String,
getStatus: (T) -> BetStatus,
getNbCoins: (T) -> Int,
getWon: (T) -> Boolean,
) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 18.dp),
verticalArrangement = Arrangement.spacedBy(18.dp),
) {
item {
Text(
text = title,
style = AllInTheme.typography.h1,
color = AllInTheme.colors.allInGrey,
fontSize = 24.sp,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
items(bets) {
BetHistoryScreenCard(
title = getTitle(it),
creator = getCreator(it),
category = getCategory(it),
date = getEndRegisterDate(it),
time = getEndBetTime(it),
status = getStatus(it),
nbCoins = getNbCoins(it),
won = getWon(it)
)
}
}
}

@ -1,13 +1,9 @@
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.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
@ -18,8 +14,6 @@ import androidx.compose.material3.SheetState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
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.tooling.preview.Preview
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.BetResultBottomSheetContentCongratulations
import fr.iut.alldev.allin.ui.core.AllInBottomSheet
import fr.iut.alldev.allin.ui.core.AllInMarqueeBox
import java.time.ZonedDateTime
@Composable
@ -76,22 +71,7 @@ fun BetResultBottomSheetContent(
odds: Float,
onClose: () -> Unit
) {
Box(
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)
)
AllInMarqueeBox(backgroundBrush = AllInTheme.colors.allInMainGradientReverse) {
Box(
modifier = Modifier
.fillMaxSize()
@ -146,7 +126,6 @@ fun BetResultBottomSheetContent(
}
@Preview
@Preview(widthDp = 800, heightDp = 1280)
@Composable
private fun BetResultBottomSheetContentPreview() {
AllInTheme {
@ -160,7 +139,7 @@ private fun BetResultBottomSheetContentPreview() {
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = true,
betStatus = BetStatus.InProgress,
betStatus = BetStatus.IN_PROGRESS,
creator = "creator",
),
stake = 4175,

@ -4,7 +4,6 @@ import android.content.res.Configuration
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import fr.iut.alldev.allin.data.model.bet.BetFinishedStatus
import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.bet.BetCard
@ -51,7 +50,7 @@ private fun BetResultBottomSheetBetCardPreview() {
title = "Title",
date = "Date",
time = "Time",
status = BetStatus.Finished(BetFinishedStatus.WON),
status = BetStatus.FINISHED,
stake = 2446,
winnings = 6930,
odds = 2.3f

@ -1,9 +1,21 @@
package fr.iut.alldev.allin.ui.betStatus
import androidx.compose.animation.*
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SheetState
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableIntState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import fr.iut.alldev.allin.data.model.bet.BetStatus
@ -56,7 +68,7 @@ fun BetStatusBottomSheet(
scrimColor = Color.Transparent
) {
var selectedAnswer by remember { mutableStateOf(0) }
var selectedAnswer by remember { mutableIntStateOf(0) }
var stake by remember { mutableStateOf<Int?>(null) }
Column(
@ -67,7 +79,9 @@ fun BetStatusBottomSheet(
displayBet(it)
BetStatusParticipationBottomSheet(
sheetVisibility = participateSheetVisibility && betDetail.bet.betStatus == BetStatus.Waiting && state.hasExpandedState,
sheetVisibility = participateSheetVisibility &&
betDetail.bet.betStatus == BetStatus.IN_PROGRESS &&
state.hasExpandedState,
safeBottomPadding = paddingValues.calculateBottomPadding(),
odds = betDetail.answers.getOrNull(selectedAnswer)?.odds ?: 1f,
betPhrase = betDetail.bet.phrase,

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

@ -2,7 +2,19 @@ package fr.iut.alldev.allin.ui.betStatus.vo
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
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.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
@ -12,9 +24,12 @@ import androidx.compose.material.icons.filled.WorkspacePremium
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
@ -64,7 +79,8 @@ class BetStatusBottomSheetBetDisplayer(
) {
val safeBottomPadding = paddingValues.value.calculateBottomPadding()
val configuration = LocalConfiguration.current
val locale = remember { ConfigurationCompat.getLocales(configuration).get(0) ?: Locale.getDefault() }
val locale =
remember { ConfigurationCompat.getLocales(configuration).get(0) ?: Locale.getDefault() }
val response1Answer = remember { betDetail.getAnswerOfResponse(response1) }
val response2Answer = remember { betDetail.getAnswerOfResponse(response2) }
@ -98,7 +114,7 @@ class BetStatusBottomSheetBetDisplayer(
}
Spacer(modifier = Modifier.height(20.dp))
}
if (betDetail.bet.betStatus is BetStatus.Finished) {
if (betDetail.bet.betStatus == BetStatus.FINISHED) {
BetStatusWinner(
answer = response1Display,
color = AllInTheme.colors.allInBlue,
@ -118,8 +134,10 @@ class BetStatusBottomSheetBetDisplayer(
Spacer(modifier = Modifier.height(20.dp))
BinaryStatBar(
response1Percentage = remember {
val total = (response1Answer?.totalParticipants ?: 0) + (response2Answer?.totalParticipants ?: 0)
if (total == 0) .5f else (response1Answer?.totalParticipants ?: 0) / total.toFloat()
val total = (response1Answer?.totalParticipants
?: 0) + (response2Answer?.totalParticipants ?: 0)
if (total == 0) .5f else (response1Answer?.totalParticipants
?: 0) / total.toFloat()
},
response1 = response1Display,
response2 = response2Display
@ -131,17 +149,17 @@ class BetStatusBottomSheetBetDisplayer(
noText = response2Answer?.totalStakes?.toString() ?: "0"
)
YesNoDetailsLine(
icon = Icons.Filled.People,
icon = rememberVectorPainter(image = Icons.Filled.People),
yesText = response1Answer?.totalParticipants?.toString() ?: "0",
noText = response2Answer?.totalParticipants?.toString() ?: "0"
)
YesNoDetailsLine(
icon = Icons.Filled.WorkspacePremium,
icon = rememberVectorPainter(image = Icons.Filled.WorkspacePremium),
yesText = response1Answer?.highestStake?.toString() ?: "0",
noText = response2Answer?.highestStake?.toString() ?: "0"
)
YesNoDetailsLine(
icon = Icons.Filled.EmojiEvents,
icon = rememberVectorPainter(image = Icons.Filled.EmojiEvents),
yesText = "x${response1Answer?.odds?.formatToSimple(locale) ?: "1.00"}",
noText = "x${response2Answer?.odds?.formatToSimple(locale) ?: "1.00"}"
)
@ -183,13 +201,13 @@ class BetStatusBottomSheetBetDisplayer(
}
}
}
if (betDetail.bet.betStatus !is BetStatus.Finished && betDetail.userParticipation == null) {
if (betDetail.bet.betStatus != BetStatus.FINISHED && betDetail.userParticipation == null) {
RainbowButton(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(horizontal = 7.dp),
text = stringResource(id = R.string.Participate),
enabled = betDetail.bet.betStatus == BetStatus.Waiting,
enabled = betDetail.bet.betStatus == BetStatus.IN_PROGRESS,
onClick = openParticipateSheet
)
}

@ -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")
}
}
}

@ -68,8 +68,8 @@ private fun BetCardPreview() {
title = "Title",
date = "Date",
time = "Time",
status = BetStatus.Waiting
){
status = BetStatus.WAITING
) {
Text("Content")
}
}

@ -2,16 +2,30 @@ package fr.iut.alldev.allin.ui.main
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.DrawerState
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SheetValue
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.rememberDrawerState
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.betResult.BetResultBottomSheet
import fr.iut.alldev.allin.ui.betStatus.BetStatusBottomSheet
import fr.iut.alldev.allin.ui.betStatus.vo.BetStatusBottomSheetBetDisplayer
import fr.iut.alldev.allin.ui.core.AllInLoading
@ -32,28 +46,6 @@ private val topLevelDestinations = listOf(
TopLevelDestination.CurrentBets
)
@Composable
private fun rememberBetStatusVisibilities()
: Triple<MutableState<Boolean>, MutableState<Boolean>, (Boolean) -> Unit> {
val statusVisibility = remember {
mutableStateOf(false)
}
val sheetBackVisibility = remember {
mutableStateOf(false)
}
val setStatusVisibility = { it: Boolean ->
statusVisibility.value = it
if (it) sheetBackVisibility.value = true
}
return Triple(
statusVisibility,
sheetBackVisibility,
setStatusVisibility,
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen(
@ -65,16 +57,19 @@ fun MainScreen(
) {
val scope = rememberCoroutineScope()
var loading by remember { mainViewModel.loading }
val (loading, setLoading) = remember { mainViewModel.loading }
val currentUser = remember { mainViewModel.currentUserState }
val selectedBet by remember { mainViewModel.selectedBet }
val (wonBet, setWonBet) = remember { mainViewModel.wonBet }
val (statusVisibility, sheetBackVisibility, setStatusVisibility) = rememberBetStatusVisibilities()
val (participateSheetVisibility, setParticipateSheetVisibility) = remember {
mutableStateOf(false)
val statusVisibility = remember { mutableStateOf(false) }
val sheetBackVisibility = remember { mutableStateOf(false) }
val setStatusVisibility = { it: Boolean ->
statusVisibility.value = it
if (it) sheetBackVisibility.value = true
}
val (participateSheetVisibility, setParticipateSheetVisibility) = remember { mutableStateOf(false) }
val (displayResult, setDisplayResult) = remember { mutableStateOf(true) }
val events = remember { mainViewModel.events }
val eventBottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val betStatusDisplayer = remember {
BetStatusBottomSheetBetDisplayer(
@ -113,10 +108,6 @@ fun MainScreen(
}
)
val resultBottomSheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
)
AllInDrawer(
drawerState = drawerState,
destinations = topLevelDestinations,
@ -126,6 +117,7 @@ fun MainScreen(
nbBets = 35,
bestWin = 362,
navigateTo = { route ->
mainViewModel.fetchEvents()
navController.popUpTo(route, startDestination)
},
logout = {
@ -135,7 +127,7 @@ fun MainScreen(
) {
AllInScaffold(
onMenuClicked = { scope.launch { drawerState.open() } },
coinAmount = currentUser.userCoins.value,
coinAmount = currentUser.userCoins.intValue,
drawerState = drawerState,
snackbarHostState = snackbarHostState
) {
@ -156,25 +148,26 @@ fun MainScreen(
setParticipateSheetVisibility(participate)
setStatusVisibility(true)
},
setLoading = { loading = it },
putSnackbarContent = { mainViewModel.putSnackbarContent(it) }
setLoading = setLoading,
putSnackbarContent = { mainViewModel.putSnackbarContent(it) },
backHandlers = {
BackHandler(enabled = drawerState.isOpen) {
scope.launch {
drawerState.close()
}
}
}
)
}
}
}
wonBet?.let {
BetResultBottomSheet(
state = resultBottomSheetState,
sheetVisibility = displayResult,
onDismiss = { setDisplayResult(false) },
bet = wonBet,
username = currentUser.user.username,
coinAmount = 1630,
stake = 1630,
winnings = 1630,
odds = 3.62f
)
events.firstOrNull()?.let {
it.Display(sheetState = eventBottomSheetState) {
mainViewModel.dismissedEvents += it
events.removeFirstOrNull()
}
}
BetStatusBottomSheet(
@ -191,14 +184,6 @@ fun MainScreen(
setParticipateSheetVisibility = setParticipateSheetVisibility
)
AllInLoading(visible = loading)
BackHandler(
enabled = drawerState.isOpen
) {
scope.launch {
drawerState.close()
}
}
}

@ -2,6 +2,7 @@ package fr.iut.alldev.allin.ui.main
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
@ -14,6 +15,9 @@ import fr.iut.alldev.allin.data.repository.BetRepository
import fr.iut.alldev.allin.di.AllInCurrentUser
import fr.iut.alldev.allin.keystore.AllInKeystoreManager
import fr.iut.alldev.allin.ui.core.snackbar.SnackbarType
import fr.iut.alldev.allin.ui.main.event.AllInEvent
import fr.iut.alldev.allin.ui.main.event.ToConfirmBet
import fr.iut.alldev.allin.ui.main.event.WonBet
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -34,28 +38,49 @@ class MainViewModel @Inject constructor(
val currentUserState = UserState(currentUser)
val selectedBet = mutableStateOf<BetDetail?>(null)
val wonBet = mutableStateOf<Bet?>(
null
/* YesNoBet(
id = "1",
theme = "Theme",
phrase = "Phrase",
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = true,
betStatus = BetStatus.Finished(BetFinishedStatus.WON),
creator = "creator"
)*/
)
val dismissedEvents = mutableStateListOf<AllInEvent>()
val events = mutableStateListOf<AllInEvent>()
val snackbarContent: MutableState<SnackbarContent?> by lazy { mutableStateOf(null) }
fun putSnackbarContent(content: SnackbarContent) {
snackbarContent.value = content
}
init {
fetchEvents()
}
fun fetchEvents() {
viewModelScope.launch {
val token = keystoreManager.getTokenOrEmpty()
events.addAll(
buildList {
addAll(betRepository.getToConfirm(token).map { bet ->
ToConfirmBet(
betDetail = bet,
onConfirm = {
confirmBet(
response = it,
betId = bet.bet.id
)
}
)
})
addAll(betRepository.getWon(token).map { result ->
WonBet(
user = currentUser,
betResult = result
)
})
}.filter { it !in dismissedEvents }
)
}
}
fun openBetDetail(bet: Bet) {
viewModelScope.launch {
selectedBet.value = betRepository.getBet(bet.id, keystoreManager.getToken() ?: "")
selectedBet.value = betRepository.getBet(bet.id, keystoreManager.getTokenOrEmpty())
}
}
@ -77,13 +102,23 @@ class MainViewModel @Inject constructor(
response = response,
stake = stake
)
betRepository.participateToBet(participation, keystoreManager.getToken() ?: "")
betRepository.participateToBet(participation, keystoreManager.getTokenOrEmpty())
}
loading.value = false
}
}
}
private fun confirmBet(response: String, betId: String) {
viewModelScope.launch {
betRepository.confirmBet(
token = keystoreManager.getTokenOrEmpty(),
id = betId,
response = response
)
}
}
class SnackbarContent(
val text: String,
val type: SnackbarType = SnackbarType.STANDARD

@ -0,0 +1,12 @@
package fr.iut.alldev.allin.ui.main.event
import androidx.compose.material3.SheetState
import androidx.compose.runtime.Composable
sealed class AllInEvent {
@Composable
abstract fun Display(
sheetState: SheetState,
onDismiss: () -> Unit
)
}

@ -0,0 +1,39 @@
package fr.iut.alldev.allin.ui.main.event
import androidx.compose.material3.SheetState
import androidx.compose.runtime.Composable
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
import fr.iut.alldev.allin.ui.betConfirmation.BetConfirmationBottomSheet
data class ToConfirmBet(
private val betDetail: BetDetail,
private val onConfirm: (String) -> Unit
) : AllInEvent() {
@Composable
override fun Display(
sheetState: SheetState,
onDismiss: () -> Unit
) {
BetConfirmationBottomSheet(
state = sheetState,
sheetVisibility = true,
betDetail = betDetail,
onDismiss = onDismiss,
onConfirm = onConfirm
)
}
override fun hashCode(): Int {
return betDetail.hashCode()
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ToConfirmBet
return betDetail == other.betDetail
}
}

@ -0,0 +1,31 @@
package fr.iut.alldev.allin.ui.main.event
import androidx.compose.material3.SheetState
import androidx.compose.runtime.Composable
import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.data.model.bet.BetResultDetail
import fr.iut.alldev.allin.ui.betResult.BetResultBottomSheet
data class WonBet(
private val user: User,
private val betResult: BetResultDetail,
) : AllInEvent() {
@Composable
override fun Display(
sheetState: SheetState,
onDismiss: () -> Unit
) {
BetResultBottomSheet(
state = sheetState,
sheetVisibility = true,
onDismiss = onDismiss,
bet = betResult.bet,
username = user.username,
coinAmount = betResult.amount,
stake = betResult.participation.stake,
winnings = betResult.amount,
odds = 1f
)
}
}

@ -12,19 +12,17 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.bet.BetScreen
import fr.iut.alldev.allin.ui.betCreation.BetCreationScreen
import fr.iut.alldev.allin.ui.betHistory.BetCurrentScreen
import fr.iut.alldev.allin.ui.betHistory.BetHistoryScreen
import fr.iut.alldev.allin.ui.core.snackbar.SnackbarType
import fr.iut.alldev.allin.ui.login.LoginScreen
@ -41,14 +39,11 @@ object Routes {
const val PUBLIC_BETS = "PUBLIC_BETS"
const val BET_CREATION = "BET_CREATION"
const val BET_HISTORY = "BET_HISTORY"
const val BET_CURRENT = "BET_CURRENT"
const val FRIENDS = "FRIENDS"
}
object NavArguments {
const val ARG_BET_HISTORY_IS_CURRENT = "ARG_BET_HISTORY_IS_CURRENT"
}
internal fun NavHostController.popUpTo(route: String, baseRoute: String) {
this.navigate(route) {
launchSingleTop = true
@ -99,7 +94,8 @@ internal fun AllInDrawerNavHost(
selectBet: (Bet, Boolean) -> Unit,
startDestination: String = Routes.PUBLIC_BETS,
setLoading: (Boolean) -> Unit,
putSnackbarContent: (MainViewModel.SnackbarContent) -> Unit
putSnackbarContent: (MainViewModel.SnackbarContent) -> Unit,
backHandlers: @Composable () -> Unit
) {
NavHost(
navController = navController,
@ -108,12 +104,15 @@ internal fun AllInDrawerNavHost(
enterTransition = { EnterTransition.None },
exitTransition = { ExitTransition.None }
) {
composable(route = Routes.PUBLIC_BETS) {
backHandlers()
BetScreen(
selectBet = selectBet
)
}
composable(route = Routes.BET_CREATION) {
backHandlers()
val creationSuccessMessage = stringResource(id = R.string.bet_creation_success_message)
BetCreationScreen(
setLoading = setLoading,
@ -130,20 +129,17 @@ internal fun AllInDrawerNavHost(
}
composable(
route = "${Routes.BET_HISTORY}/{${NavArguments.ARG_BET_HISTORY_IS_CURRENT}}",
arguments = listOf(
navArgument(NavArguments.ARG_BET_HISTORY_IS_CURRENT) {
type = NavType.BoolType
}
)
route = Routes.BET_HISTORY
) {
backHandlers()
BetHistoryScreen()
}
composable(
route = Routes.BET_CURRENT
) {
val isCurrent =
it.arguments?.getBoolean(NavArguments.ARG_BET_HISTORY_IS_CURRENT) ?: false
BetHistoryScreen(
isCurrent = isCurrent,
viewModel = hiltViewModel(it, isCurrent.toString())
)
backHandlers()
BetCurrentScreen()
}
}
}

@ -23,7 +23,7 @@ sealed class TopLevelDestination(
)
data object BetHistory : TopLevelDestination(
route = "${Routes.BET_HISTORY}/false",
route = Routes.BET_HISTORY,
title = R.string.bet_history,
subtitle = R.string.bet_history_subtitle,
emoji = R.drawable.eyes
@ -37,7 +37,7 @@ sealed class TopLevelDestination(
)
data object CurrentBets : TopLevelDestination(
route = "${Routes.BET_HISTORY}/true",
route = Routes.BET_CURRENT,
title = R.string.current_bets,
subtitle = R.string.current_bets_subtitle,
emoji = R.drawable.money_with_wings

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

@ -2,7 +2,6 @@ package fr.iut.alldev.allin.ui.preview
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
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.CustomBet
import fr.iut.alldev.allin.data.model.bet.MatchBet
@ -18,7 +17,7 @@ class BetPreviewProvider : PreviewParameterProvider<Bet> {
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = true,
betStatus = BetStatus.Finished(BetFinishedStatus.WON),
betStatus = BetStatus.FINISHED,
creator = "creator"
),
MatchBet(
@ -28,7 +27,7 @@ class BetPreviewProvider : PreviewParameterProvider<Bet> {
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = true,
betStatus = BetStatus.Finished(BetFinishedStatus.WON),
betStatus = BetStatus.FINISHED,
creator = "creator",
nameTeam1 = "The Monarchs",
nameTeam2 = "Climate Change"
@ -40,7 +39,7 @@ class BetPreviewProvider : PreviewParameterProvider<Bet> {
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = true,
betStatus = BetStatus.Finished(BetFinishedStatus.WON),
betStatus = BetStatus.FINISHED,
creator = "creator",
possibleAnswers = listOf(
"Answer 1",

@ -4,5 +4,5 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import fr.iut.alldev.allin.data.model.bet.BetStatus
class BetStatusPreviewProvider : PreviewParameterProvider<BetStatus> {
override val values = BetStatus.entries
override val values = BetStatus.entries.asSequence()
}

@ -10,7 +10,7 @@ import java.time.ZonedDateTime
class BetWithStatusPreviewProvider : PreviewParameterProvider<Bet> {
override val values = BetStatus.entries.flatMap { status ->
override val values = BetStatus.entries.asSequence().flatMap { status ->
sequenceOf(
YesNoBet(
id = "1",

@ -14,6 +14,7 @@
<string name="Validate">Valider</string>
<string name="Cancel">Annuler</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_BadFormat">Le %s a un mauvais format : %s.</string>
<string name="FieldError_NotIdentical">Les champs ne sont pas identiques.</string>
@ -117,6 +118,8 @@
<string name="bet_status_finished">Terminé !</string>
<string name="bet_status_in_progress">En cours…</string>
<string name="bet_status_waiting">En attente…</string>
<string name="bet_status_closing">Fermeture…</string>
<string name="bet_status_cancelled">Annulé</string>
<string name="place_your_bets">Faites vos paris</string>
<string name="bet_status_participants_list">Liste des participants</string>

@ -16,6 +16,7 @@
<string name="Validate">Validate</string>
<string name="Cancel">Cancel</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_BadFormat">The %s has bad format : %s.</string>
<string name="FieldError_NotIdentical">The fields are not identical.</string>
@ -117,6 +118,8 @@
<string name="bet_status_finished">Finished !</string>
<string name="bet_status_in_progress">In progress…</string>
<string name="bet_status_waiting">Waiting…</string>
<string name="bet_status_closing">Closing…</string>
<string name="bet_status_cancelled">Cancelled</string>
<string name="place_your_bets">Place your bets</string>
<string name="bet_status_participants_list">Participants</string>

@ -6,6 +6,7 @@ import fr.iut.alldev.allin.data.api.model.RequestParticipation
import fr.iut.alldev.allin.data.api.model.RequestUser
import fr.iut.alldev.allin.data.api.model.ResponseBet
import fr.iut.alldev.allin.data.api.model.ResponseBetDetail
import fr.iut.alldev.allin.data.api.model.ResponseBetResultDetail
import fr.iut.alldev.allin.data.api.model.ResponseUser
import retrofit2.http.Body
import retrofit2.http.GET
@ -31,11 +32,42 @@ interface AllInApi {
suspend fun createBet(@Header("Authorization") token: String, @Body body: RequestBet)
@GET("bets/gets")
suspend fun getAllBets(): List<ResponseBet>
suspend fun getAllBets(@Header("Authorization") token: String): List<ResponseBet>
@GET("bets/toConfirm")
suspend fun getToConfirm(@Header("Authorization") token: String): List<ResponseBetDetail>
@POST("bets/confirm/{id}")
suspend fun confirmBet(
@Header("Authorization") token: String,
@Path("id") id: String,
@Body value: String
)
@GET("betdetail/get/{id}")
suspend fun getBet(@Header("Authorization") token: String, @Path("id") id: String): ResponseBetDetail
suspend fun getBet(
@Header("Authorization") token: String,
@Path("id") id: String
): ResponseBetDetail
@GET("bets/getCurrent")
suspend fun getBetCurrent(
@Header("Authorization") token: String
): List<ResponseBetDetail>
@GET("bets/history")
suspend fun getBetHistory(
@Header("Authorization") token: String
): List<ResponseBetResultDetail>
@GET("bets/getWon")
suspend fun getWon(
@Header("Authorization") token: String
): List<ResponseBetResultDetail>
@POST("participations/add")
suspend fun participateToBet(@Header("Authorization") token: String, @Body body: RequestParticipation)
suspend fun participateToBet(
@Header("Authorization") token: String,
@Body body: RequestParticipation
)
}

@ -8,19 +8,26 @@ import fr.iut.alldev.allin.data.api.model.RequestUser
import fr.iut.alldev.allin.data.api.model.ResponseBet
import fr.iut.alldev.allin.data.api.model.ResponseBetAnswerDetail
import fr.iut.alldev.allin.data.api.model.ResponseBetDetail
import fr.iut.alldev.allin.data.api.model.ResponseBetResultDetail
import fr.iut.alldev.allin.data.api.model.ResponseParticipation
import fr.iut.alldev.allin.data.api.model.ResponseUser
import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.data.model.bet.BetType
import fr.iut.alldev.allin.data.model.bet.NO_VALUE
import fr.iut.alldev.allin.data.model.bet.YES_VALUE
import fr.iut.alldev.allin.data.model.bet.vo.BetResult
import java.time.ZonedDateTime
import java.util.UUID
class MockAllInApi : AllInApi {
private fun getUserFromToken(token: String) =
mockUsers.find { it.first.token == token }
mockUsers.find { it.first.token == token.removePrefix("Bearer ") }
private fun getAnswerDetails(bet: ResponseBet, participations: List<ResponseParticipation>): List<ResponseBetAnswerDetail> {
private fun getAnswerDetails(
bet: ResponseBet,
participations: List<ResponseParticipation>
): List<ResponseBetAnswerDetail> {
return bet.response.map { response ->
val responseParticipations = participations.filter { it.answer == response }
ResponseBetAnswerDetail(
@ -65,12 +72,47 @@ class MockAllInApi : AllInApi {
endBet = body.endBet,
isPrivate = body.isPrivate,
response = body.response,
type = BetType.BINARY,
status = BetStatus.WAITING,
createdBy = ""
)
)
}
override suspend fun getAllBets(): List<ResponseBet> = mockBets.toList()
override suspend fun getAllBets(token: String): List<ResponseBet> {
getUserFromToken(token) ?: throw AllInAPIException("Invalid login/password.")
return mockBets
}
override suspend fun getToConfirm(token: String): List<ResponseBetDetail> {
val user = getUserFromToken(token) ?: throw AllInAPIException("Invalid login/password.")
return mockBets.filter {
it.createdBy == user.first.username && it.status == BetStatus.CLOSING
}.map { bet ->
val betParticipations = mockParticipations.filter { it.betId == bet.id }
val userParticipation = betParticipations.find { it.username == user.first.username }
ResponseBetDetail(
bet = bet,
answers = getAnswerDetails(bet, betParticipations),
participations = betParticipations,
userParticipation = userParticipation
)
}
}
override suspend fun confirmBet(token: String, id: String, value: String) {
getUserFromToken(token) ?: throw AllInAPIException("Invalid login/password.")
val bet = mockBets.find { it.id == id } ?: throw AllInAPIException("Unauthorized")
mockResults.add(
BetResult(
betId = id,
result = value
)
)
mockBets[mockBets.indexOf(bet)] = bet.copy(status = BetStatus.FINISHED)
}
override suspend fun getBet(token: String, id: String): ResponseBetDetail {
val bet = mockBets.find { it.id == id } ?: throw AllInAPIException("Bet not found")
val user = getUserFromToken(token) ?: throw AllInAPIException("Invalid login/password.")
@ -85,6 +127,18 @@ class MockAllInApi : AllInApi {
)
}
override suspend fun getBetCurrent(token: String): List<ResponseBetDetail> {
return emptyList()
}
override suspend fun getBetHistory(token: String): List<ResponseBetResultDetail> {
return emptyList()
}
override suspend fun getWon(token: String): List<ResponseBetResultDetail> {
return emptyList()
}
override suspend fun participateToBet(token: String, body: RequestParticipation) {
getUserFromToken(token)?.let {
mockParticipations.add(
@ -214,7 +268,9 @@ private val mockBets = mutableListOf(
endBet = ZonedDateTime.now().plusDays(4),
isPrivate = false,
response = listOf(YES_VALUE, NO_VALUE),
createdBy = "Armure"
createdBy = "Armure",
type = BetType.BINARY,
status = BetStatus.WAITING,
),
ResponseBet(
id = "UUID2",
@ -224,16 +280,22 @@ private val mockBets = mutableListOf(
endBet = ZonedDateTime.now().plusDays(4),
isPrivate = false,
response = listOf("Answer 1", "Answer 2", "Answer 3", "Answer 4"),
createdBy = "User 2"
createdBy = "User 2",
type = BetType.BINARY,
status = BetStatus.WAITING,
),
ResponseBet(
id = "UUID3",
theme = "Sport",
sentenceBet = "Nouveau record du monde ?",
endRegistration = ZonedDateTime.now().plusDays(3),
endBet = ZonedDateTime.now().plusDays(4),
endRegistration = ZonedDateTime.now().minusDays(3),
endBet = ZonedDateTime.now().minusDays(2),
isPrivate = false,
response = listOf(YES_VALUE, NO_VALUE),
createdBy = "Armure"
createdBy = "User 1",
type = BetType.BINARY,
status = BetStatus.CLOSING,
)
)
private val mockResults by lazy { mutableListOf<BetResult>() }

@ -2,7 +2,10 @@ package fr.iut.alldev.allin.data.api.model
import androidx.annotation.Keep
import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.data.model.bet.BetResult
import fr.iut.alldev.allin.data.model.bet.BetResultDetail
import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.data.model.bet.BetType
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
@ -18,6 +21,8 @@ import java.time.ZonedDateTime
data class ResponseBet(
val id: String?,
val theme: String,
val type: BetType,
val status: BetStatus,
val sentenceBet: String,
@Serializable(ZonedDateTimeSerializer::class) val endRegistration: ZonedDateTime,
@Serializable(ZonedDateTimeSerializer::class) var endBet: ZonedDateTime,
@ -34,7 +39,7 @@ data class ResponseBet(
endRegisterDate = endRegistration,
endBetDate = endBet,
isPublic = !isPrivate,
betStatus = BetStatus.Waiting,
betStatus = status,
creator = createdBy
)
} else {
@ -45,7 +50,7 @@ data class ResponseBet(
endRegisterDate = endRegistration,
endBetDate = endBet,
isPublic = !isPrivate,
betStatus = BetStatus.Waiting,
betStatus = status,
creator = createdBy,
possibleAnswers = response
)
@ -58,6 +63,7 @@ data class ResponseBet(
data class RequestBet(
val id: String = "",
val theme: String,
val type: BetType,
val sentenceBet: String,
@Serializable(ZonedDateTimeSerializer::class) val endRegistration: ZonedDateTime,
@Serializable(ZonedDateTimeSerializer::class) var endBet: ZonedDateTime,
@ -101,3 +107,33 @@ data class ResponseBetDetail(
)
}
@Serializable
data class ResponseBetResult(
val betId: String,
val result: String
) {
fun toBetResult() =
BetResult(
betId = betId,
result = result
)
}
@Serializable
data class ResponseBetResultDetail(
val betResult: ResponseBetResult,
val bet: ResponseBet,
val participation: ResponseParticipation,
val amount: Int,
val won: Boolean
) {
fun toBetResultDetail() =
BetResultDetail(
betResult = betResult.toBetResult(),
bet = bet.toBet(),
participation = participation.toParticipation(),
amount = amount,
won = won
)
}

@ -25,7 +25,7 @@ data class ResponseUser(
id = id,
username = username,
email = email,
coins = nbCoins.toInt()
coins = nbCoins
)
}

@ -13,16 +13,18 @@ sealed class Bet(
open val isPublic: Boolean,
open val betStatus: BetStatus,
) {
abstract fun getBetType(): BetType
abstract fun getResponses(): List<String>
fun toRequestBet(): RequestBet {
return RequestBet(
id = "",
id = id,
theme = theme,
sentenceBet = phrase,
endRegistration = endRegisterDate,
endBet = endBetDate,
isPrivate = !isPublic,
response = getResponses()
response = getResponses(),
type = getBetType()
)
}
}

@ -19,7 +19,7 @@ class BetFactory {
): Bet =
when (betType) {
BetType.YES_NO -> {
BetType.BINARY -> {
YesNoBet(
id = id,
theme = theme,
@ -28,7 +28,7 @@ class BetFactory {
endRegisterDate = endRegisterDate,
endBetDate = endBetDate,
isPublic = isPublic,
betStatus = BetStatus.Waiting
betStatus = BetStatus.WAITING
)
}
@ -41,7 +41,7 @@ class BetFactory {
endRegisterDate = endRegisterDate,
endBetDate = endBetDate,
isPublic = isPublic,
betStatus = BetStatus.Waiting,
betStatus = BetStatus.WAITING,
nameTeam1 = nameTeam1,
nameTeam2 = nameTeam2
)
@ -57,7 +57,7 @@ class BetFactory {
endRegisterDate = endRegisterDate,
endBetDate = endBetDate,
isPublic = isPublic,
betStatus = BetStatus.Waiting,
betStatus = BetStatus.WAITING,
possibleAnswers = possibleAnswers
)
}

@ -0,0 +1,14 @@
package fr.iut.alldev.allin.data.model.bet
data class BetResult(
val betId: String,
val result: String
)
data class BetResultDetail(
val betResult: BetResult,
val bet: Bet,
val participation: Participation,
val amount: Int,
val won: Boolean
)

@ -1,19 +1,9 @@
package fr.iut.alldev.allin.data.model.bet
sealed class BetStatus {
data class Finished(val status: BetFinishedStatus) : BetStatus()
data object InProgress : BetStatus()
data object Waiting : BetStatus()
companion object {
val entries = sequenceOf(
InProgress,
Waiting,
Finished(BetFinishedStatus.WON),
Finished(BetFinishedStatus.LOST)
)
}
enum class BetStatus {
IN_PROGRESS,
WAITING,
CLOSING,
FINISHED,
CANCELLED
}

@ -1,7 +1,7 @@
package fr.iut.alldev.allin.data.model.bet
enum class BetType {
YES_NO,
BINARY,
MATCH,
CUSTOM
}

@ -22,5 +22,7 @@ data class CustomBet(
isPublic,
betStatus
) {
override fun getBetType() = BetType.CUSTOM
override fun getResponses(): List<String> = possibleAnswers
}

@ -23,6 +23,7 @@ data class MatchBet(
isPublic,
betStatus
) {
override fun getBetType() = BetType.MATCH
override fun getResponses(): List<String> = listOf(nameTeam1, nameTeam2)
}

@ -24,5 +24,6 @@ data class YesNoBet(
isPublic,
betStatus
) {
override fun getBetType() = BetType.BINARY
override fun getResponses(): List<String> = listOf(YES_VALUE, NO_VALUE)
}

@ -1,10 +1,6 @@
package fr.iut.alldev.allin.data.model.bet.vo
import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.data.model.bet.Participation
data class BetResult(
val bet: Bet,
val participations: List<Participation>,
val answerDetail: BetAnswerDetail
val betId: String,
val result: String
)

@ -1,15 +1,18 @@
package fr.iut.alldev.allin.data.repository
import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.data.model.bet.BetResultDetail
import fr.iut.alldev.allin.data.model.bet.Participation
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
import kotlinx.coroutines.flow.Flow
abstract class BetRepository {
abstract suspend fun createBet(bet: Bet, token: String)
abstract suspend fun getHistory(): Flow<List<Bet>>
abstract suspend fun getCurrentBets(): Flow<List<Bet>>
abstract suspend fun getHistory(token: String): List<BetResultDetail>
abstract suspend fun getCurrentBets(token: String): List<BetDetail>
abstract suspend fun getBet(id: String, token: String): BetDetail
abstract suspend fun participateToBet(participation: Participation, token: String)
abstract suspend fun getAllBets(): Flow<List<Bet>>
abstract suspend fun getAllBets(token: String): List<Bet>
abstract suspend fun confirmBet(token: String, id: String, response: String)
abstract suspend fun getToConfirm(token: String): List<BetDetail>
abstract suspend fun getWon(token: String): List<BetResultDetail>
}

@ -3,15 +3,10 @@ package fr.iut.alldev.allin.data.repository.impl
import fr.iut.alldev.allin.data.api.AllInApi
import fr.iut.alldev.allin.data.api.AllInApi.Companion.formatBearerToken
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.BetResultDetail
import fr.iut.alldev.allin.data.model.bet.Participation
import fr.iut.alldev.allin.data.model.bet.YesNoBet
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
import fr.iut.alldev.allin.data.repository.BetRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import java.time.ZonedDateTime
import javax.inject.Inject
class BetRepositoryImpl @Inject constructor(
@ -24,79 +19,16 @@ class BetRepositoryImpl @Inject constructor(
)
}
override suspend fun getHistory(): Flow<List<Bet>> {
return flowOf(
listOf(
YesNoBet(
id = "1",
creator = "Lucas",
theme = "Theme",
phrase = "Bet phrase 1",
endRegisterDate = ZonedDateTime.now().minusDays(4),
endBetDate = ZonedDateTime.now().minusDays(2),
isPublic = false,
betStatus = BetStatus.Finished(BetFinishedStatus.WON)
),
YesNoBet(
id = "2",
creator = "Lucas",
theme = "Theme",
phrase = "Bet phrase 2",
endRegisterDate = ZonedDateTime.now().minusDays(3),
endBetDate = ZonedDateTime.now().minusDays(1),
isPublic = true,
betStatus = BetStatus.Finished(BetFinishedStatus.LOST)
),
YesNoBet(
id = "3",
creator = "Lucas",
theme = "Theme",
phrase = "Bet phrase 3",
endRegisterDate = ZonedDateTime.now().minusDays(15),
endBetDate = ZonedDateTime.now().minusDays(7),
isPublic = false,
betStatus = BetStatus.Finished(BetFinishedStatus.LOST)
)
)
)
override suspend fun getHistory(token: String): List<BetResultDetail> {
return api.getBetHistory(token.formatBearerToken()).map {
it.toBetResultDetail()
}
}
override suspend fun getCurrentBets(): Flow<List<Bet>> {
// TODO
return flowOf(
listOf(
YesNoBet(
id = "1",
creator = "Lucas",
theme = "Theme",
phrase = "Bet phrase 1",
endRegisterDate = ZonedDateTime.now().plusDays(5),
endBetDate = ZonedDateTime.now().plusDays(7),
isPublic = false,
betStatus = BetStatus.InProgress
),
YesNoBet(
id = "2",
creator = "Lucas",
theme = "Theme",
phrase = "Bet phrase 2",
endRegisterDate = ZonedDateTime.now().plusDays(1),
endBetDate = ZonedDateTime.now().plusDays(2),
isPublic = true,
betStatus = BetStatus.InProgress
),
YesNoBet(
id = "3",
creator = "Lucas",
theme = "Theme",
phrase = "Bet phrase 3",
endRegisterDate = ZonedDateTime.now().plusDays(3),
endBetDate = ZonedDateTime.now().plusDays(4),
isPublic = false,
betStatus = BetStatus.InProgress
)
)
)
override suspend fun getCurrentBets(token: String): List<BetDetail> {
return api.getToConfirm(token.formatBearerToken()).map {
it.toBetDetail()
}
}
override suspend fun getBet(id: String, token: String): BetDetail {
@ -107,13 +39,24 @@ class BetRepositoryImpl @Inject constructor(
}
override suspend fun participateToBet(participation: Participation, token: String) {
api.participateToBet(token = token.formatBearerToken(), body = participation.toRequestParticipation())
api.participateToBet(
token = token.formatBearerToken(),
body = participation.toRequestParticipation()
)
}
override suspend fun getAllBets(): Flow<List<Bet>> {
return flowOf(
api.getAllBets().map { it.toBet() }
)
override suspend fun getAllBets(token: String): List<Bet> =
api.getAllBets(token.formatBearerToken()).map { it.toBet() }
override suspend fun getToConfirm(token: String): List<BetDetail> =
api.getToConfirm(token.formatBearerToken()).map { it.toBetDetail() }
override suspend fun getWon(token: String): List<BetResultDetail> =
api.getWon(token.formatBearerToken()).map { it.toBetResultDetail() }
override suspend fun confirmBet(token: String, id: String, response: String) {
api.confirmBet(token.formatBearerToken(), id, response)
}
}
Loading…
Cancel
Save