diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/betConfirmation/BetConfirmationBottomSheet.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/betConfirmation/BetConfirmationBottomSheet.kt index c2e11cd..043ae8c 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/betConfirmation/BetConfirmationBottomSheet.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/betConfirmation/BetConfirmationBottomSheet.kt @@ -320,7 +320,8 @@ private fun BetConfirmationBottomSheetContentPreview() { ) ), participations = emptyList(), - userParticipation = null + userParticipation = null, + wonParticipation = null ), onConfirm = { } ) { diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/BetCurrentScreen.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/BetCurrentScreen.kt index 540febe..0fea3ae 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/BetCurrentScreen.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/BetCurrentScreen.kt @@ -8,10 +8,12 @@ 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.data.model.bet.Bet import fr.iut.alldev.allin.ui.betHistory.components.GenericHistory @Composable fun BetCurrentScreen( + selectBet: (Bet, Boolean) -> Unit, viewModel: BetCurrentViewModel = hiltViewModel() ) { val bets by viewModel.bets.collectAsState() @@ -25,6 +27,7 @@ fun BetCurrentScreen( getEndBetTime = { it.bet.endBetDate.formatToTime() }, getStatus = { it.bet.betStatus }, getNbCoins = { it.userParticipation?.stake ?: 0 }, - getWon = { true } + getWon = { true }, + onClick = { selectBet(it.bet, false) }, ) } \ No newline at end of file diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/BetCurrentViewModel.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/BetCurrentViewModel.kt index 59634f5..2db3885 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/BetCurrentViewModel.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/BetCurrentViewModel.kt @@ -37,7 +37,7 @@ class BetCurrentViewModel @Inject constructor( init { viewModelScope.launch { _bets.emit( - betRepository.getToConfirm(keystoreManager.getTokenOrEmpty()) + betRepository.getCurrentBets(keystoreManager.getTokenOrEmpty()) ) } } diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/BetHistoryScreen.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/BetHistoryScreen.kt index 06c7025..c9a0538 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/BetHistoryScreen.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/BetHistoryScreen.kt @@ -8,10 +8,12 @@ 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.data.model.bet.Bet import fr.iut.alldev.allin.ui.betHistory.components.GenericHistory @Composable fun BetHistoryScreen( + selectBet: (Bet, Boolean) -> Unit, viewModel: BetHistoryViewModel = hiltViewModel() ) { val bets by viewModel.bets.collectAsState() @@ -25,6 +27,7 @@ fun BetHistoryScreen( getEndBetTime = { it.bet.endBetDate.formatToTime() }, getStatus = { it.bet.betStatus }, getNbCoins = { it.amount }, - getWon = { it.won } + getWon = { it.won }, + onClick = { selectBet(it.bet, false) } ) } \ No newline at end of file diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/components/BetHistoryScreenCard.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/components/BetHistoryScreenCard.kt index 5b633ed..3a65c70 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/components/BetHistoryScreenCard.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/components/BetHistoryScreenCard.kt @@ -13,6 +13,7 @@ import fr.iut.alldev.allin.ui.preview.BetStatusPreviewProvider @Composable fun BetHistoryScreenCard( modifier: Modifier = Modifier, + onClick: () -> Unit, title: String, creator: String, category: String, @@ -29,6 +30,7 @@ fun BetHistoryScreenCard( date = date, time = time, status = status, + onClick = onClick, modifier = modifier ) { BetHistoryBetStatus( @@ -47,6 +49,7 @@ private fun BetHistoryScreenCardPreview( ) { AllInTheme { BetHistoryScreenCard( + onClick = {}, creator = "Creator", category = "Category", title = "Title", diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/components/GenericHistory.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/components/GenericHistory.kt index 02e35f0..617a3b0 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/components/GenericHistory.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/components/GenericHistory.kt @@ -30,6 +30,7 @@ fun <T> GenericHistory( getStatus: (T) -> BetStatus, getNbCoins: (T) -> Int, getWon: (T) -> Boolean, + onClick: (T) -> Unit, ) { LazyColumn( modifier = Modifier.fillMaxSize(), @@ -56,7 +57,8 @@ fun <T> GenericHistory( time = getEndBetTime(it), status = getStatus(it), nbCoins = getNbCoins(it), - won = getWon(it) + won = getWon(it), + onClick = { onClick(it) } ) } } diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/betStatus/components/BetStatusWinner.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/betStatus/components/BetStatusWinner.kt index 98b0592..ed5d790 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/betStatus/components/BetStatusWinner.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/betStatus/components/BetStatusWinner.kt @@ -38,7 +38,7 @@ fun BetStatusWinner( modifier = Modifier .fillMaxWidth() .background(color.copy(alpha = .2f)) - .padding(vertical = 20.dp) + .padding(20.dp) ) { AllInTextIcon( text = answer, diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/betStatus/vo/BetStatusBottomSheetBetDisplayer.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/betStatus/vo/BetStatusBottomSheetBetDisplayer.kt index 0be24b9..599f40f 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/betStatus/vo/BetStatusBottomSheetBetDisplayer.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/betStatus/vo/BetStatusBottomSheetBetDisplayer.kt @@ -85,6 +85,7 @@ class BetStatusBottomSheetBetDisplayer( private fun DisplayBet( betDetail: BetDetail, currentUser: User, + winnerColor: @Composable () -> Color, statBar: LazyListScope.() -> Unit ) { Box(Modifier) { @@ -117,13 +118,16 @@ class BetStatusBottomSheetBetDisplayer( Spacer(modifier = Modifier.height(20.dp)) } if (betDetail.bet.betStatus == BetStatus.FINISHED) { - BetStatusWinner( - answer = YES_VALUE, - color = AllInColorToken.allInBlue, - coinAmount = 442, - username = "Imri", - multiplier = 1.2f - ) + betDetail.wonParticipation?.let { wonParticipation -> + BetStatusWinner( + answer = wonParticipation.response, + color = winnerColor(), + coinAmount = wonParticipation.stake, + username = wonParticipation.username, + multiplier = betDetail.getAnswerOfResponse(wonParticipation.response) + ?.odds ?: .5f + ) + } } else { HorizontalDivider(color = AllInTheme.colors.border) } @@ -301,13 +305,20 @@ class BetStatusBottomSheetBetDisplayer( @Composable override fun DisplayYesNoBet(betDetail: BetDetail, currentUser: User) { - DisplayBet(betDetail = betDetail, currentUser = currentUser) { + DisplayBet( + betDetail = betDetail, + currentUser = currentUser, + winnerColor = { + if (betDetail.wonParticipation?.response == YES_VALUE) AllInColorToken.allInBlue + else AllInColorToken.allInPink + } + ) { displayBinaryStatBar( betDetail = betDetail, response1 = YES_VALUE, response2 = NO_VALUE, response1Display = { stringResource(id = R.string.Yes).uppercase() }, - response2Display = { stringResource(id = R.string.No).uppercase() } + response2Display = { stringResource(id = R.string.No).uppercase() }, ) } } @@ -316,7 +327,14 @@ class BetStatusBottomSheetBetDisplayer( override fun DisplayMatchBet(betDetail: BetDetail, currentUser: User) { val matchBet = remember { betDetail.bet as MatchBet } - DisplayBet(betDetail = betDetail, currentUser = currentUser) { + DisplayBet( + betDetail = betDetail, + currentUser = currentUser, + winnerColor = { + if (betDetail.wonParticipation?.response == matchBet.nameTeam1) AllInColorToken.allInBlue + else AllInColorToken.allInPink + } + ) { displayBinaryStatBar( betDetail = betDetail, response1 = matchBet.nameTeam1, @@ -331,7 +349,18 @@ class BetStatusBottomSheetBetDisplayer( val configuration = LocalConfiguration.current val locale = remember { ConfigurationCompat.getLocales(configuration).get(0) ?: Locale.getDefault() } - DisplayBet(betDetail = betDetail, currentUser = currentUser) { + DisplayBet( + betDetail = betDetail, + currentUser = currentUser, + winnerColor = { + val isBinary = remember { customBet.possibleAnswers.size == 2 } + + if (isBinary) { + if (betDetail.wonParticipation?.response == customBet.possibleAnswers.first()) AllInColorToken.allInBlue + else AllInColorToken.allInPink + } else AllInTheme.colors.onMainSurface + } + ) { if (customBet.possibleAnswers.size == 2) { displayBinaryStatBar( betDetail = betDetail, diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/core/AllInTextIcon.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/core/AllInTextIcon.kt index cee3673..bf0360d 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/core/AllInTextIcon.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/core/AllInTextIcon.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp @@ -57,7 +58,10 @@ fun AllInTextIcon( text = text, color = color, style = brush?.let { textStyle.copy(brush = it) } ?: textStyle, - fontSize = size.sp + fontSize = size.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f, fill = false) ) Icon( painter = icon, diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/core/bet/BetCard.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/core/bet/BetCard.kt index 150780d..030883e 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/core/bet/BetCard.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/core/bet/BetCard.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.unit.dp import fr.iut.alldev.allin.data.model.bet.BetStatus import fr.iut.alldev.allin.ext.getDateStartLabelId import fr.iut.alldev.allin.theme.AllInTheme +import fr.iut.alldev.allin.ui.core.AllInBouncyCard import fr.iut.alldev.allin.ui.core.AllInCard @Composable @@ -57,6 +58,47 @@ fun BetCard( } } +@Composable +fun BetCard( + modifier: Modifier = Modifier, + title: String, + creator: String, + category: String, + date: String, + time: String, + status: BetStatus, + onClick: () -> Unit, + content: @Composable () -> Unit +) { + AllInBouncyCard( + modifier = modifier.fillMaxWidth(), + radius = 16.dp, + onClick = onClick + ) { + Column( + Modifier.padding(horizontal = 19.dp, vertical = 11.dp) + ) { + BetTitleHeader( + title = title, + category = category, + creator = creator, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(11.dp)) + BetDateTimeRow( + label = stringResource(id = status.getDateStartLabelId()), + date = date, + time = time + ) + } + HorizontalDivider( + thickness = 1.dp, + color = AllInTheme.colors.border + ) + content() + } +} + @Preview @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/core/topbar/AllInTopBarCoinCounter.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/core/topbar/AllInTopBarCoinCounter.kt index f132eb7..530db80 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/core/topbar/AllInTopBarCoinCounter.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/core/topbar/AllInTopBarCoinCounter.kt @@ -1,5 +1,10 @@ package fr.iut.alldev.allin.ui.core.topbar +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.core.tween +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row @@ -10,6 +15,11 @@ import androidx.compose.material3.Card import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -27,6 +37,14 @@ fun AllInTopBarCoinCounter( textColor: Color = AllInColorToken.allInDark, iconColor: Color = AllInColorToken.allInBlue, ) { + var oldAmount by remember { mutableIntStateOf(amount) } + LaunchedEffect(amount) { + oldAmount = amount + } + + val countString = remember(amount) { amount.toString() } + val oldCountString = remember(oldAmount) { oldAmount.toString() } + Card( modifier = modifier.wrapContentSize(), shape = RoundedCornerShape(topStartPercent = 50, bottomStartPercent = 50) @@ -34,20 +52,45 @@ fun AllInTopBarCoinCounter( Row( modifier = Modifier .background(backgroundColor) - .padding(horizontal = 13.dp, vertical = 5.dp), + .padding(horizontal = 13.dp), horizontalArrangement = Arrangement.spacedBy(7.dp), verticalAlignment = Alignment.CenterVertically ) { - Text( - text = amount.toString(), - color = textColor, - style = AllInTheme.typography.h1, - fontSize = 20.sp - ) + Row { + for (i in countString.indices) { + val oldChar = oldCountString.getOrNull(i) + val newChar = countString[i] + val char = if (oldChar == newChar) { + oldCountString[i] + } else { + countString[i] + } + + AnimatedContent( + targetState = char, + transitionSpec = { + val delayMillis = (countString.indices.count() - i) * 50 + (slideInVertically(tween(delayMillis)) { it } togetherWith + slideOutVertically(tween(delayMillis)) { -it }) + }, + label = "" + ) { char -> + Text( + text = char.toString(), + style = AllInTheme.typography.h1, + color = textColor, + fontSize = 20.sp, + softWrap = false, + modifier = Modifier.padding(vertical = 5.dp) + ) + } + } + } Icon( painter = AllInTheme.icons.allCoins(), tint = iconColor, contentDescription = null, + modifier = Modifier.padding(vertical = 5.dp) ) } } diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainScreen.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainScreen.kt index 6ddc905..0a0dae8 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainScreen.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainScreen.kt @@ -72,7 +72,6 @@ fun MainScreen( val (loading, setLoading) = remember { mainViewModel.loading } val currentUser by mainViewModel.currentUser.collectAsStateWithLifecycle() - val userCoins by mainViewModel.userCoins.collectAsStateWithLifecycle() val selectedBet by remember { mainViewModel.selectedBet } val statusVisibility = remember { mutableStateOf(false) } val sheetBackVisibility = remember { mutableStateOf(false) } @@ -141,7 +140,7 @@ fun MainScreen( ) { AllInScaffold( onMenuClicked = { scope.launch { drawerState.open() } }, - coinAmount = userCoins ?: 0, + coinAmount = currentUser?.coins ?: 0, drawerState = drawerState, snackbarHostState = snackbarHostState ) { @@ -200,6 +199,7 @@ fun MainScreen( scope.launch { eventBottomSheetState.hide() delay(EVENT_DISMISS_DELAY_MS) + mainViewModel.increaseCoins(event.betResult.amount) events.removeFirstOrNull() } } @@ -215,6 +215,7 @@ fun MainScreen( (events.firstOrNull() as? DailyReward)?.let { it.Display( onDismiss = { + mainViewModel.increaseCoins(it.amount) dailyRewardVisible = false mainViewModel.dismissedEvents += it scope.launch { @@ -238,7 +239,7 @@ fun MainScreen( displayBet = { detail -> currentUser?.let { user -> betStatusDisplayer.DisplayBet(betDetail = detail, currentUser = user) } }, - userCoinAmount = userCoins ?: 0, + userCoinAmount = currentUser?.coins ?: 0, onParticipate = { stake, response -> mainViewModel.participateToBet(stake, response) }, participateSheetVisibility = participateSheetVisibility, setParticipateSheetVisibility = setParticipateSheetVisibility diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainViewModel.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainViewModel.kt index a528cfb..da4b448 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainViewModel.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainViewModel.kt @@ -18,7 +18,6 @@ import fr.iut.alldev.allin.ui.main.event.DailyReward 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.flow.asStateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import javax.inject.Inject @@ -32,8 +31,7 @@ class MainViewModel @Inject constructor( var loading = mutableStateOf(false) - val currentUser = userRepository.currentUser.asStateFlow() - val userCoins = userRepository.userCoins.asStateFlow() + val currentUser = userRepository.currentUser val selectedBet = mutableStateOf<BetDetail?>(null) val dismissedEvents = mutableStateListOf<AllInEvent>() val events = mutableStateListOf<AllInEvent>() @@ -53,27 +51,34 @@ class MainViewModel @Inject constructor( val token = keystoreManager.getTokenOrEmpty() events.addAll( buildList { - add(DailyReward(125)) - - addAll(betRepository.getToConfirm(token).map { bet -> - ToConfirmBet( - betDetail = bet, - onConfirm = { - confirmBet( - response = it, - betId = bet.bet.id + runCatching { + add(DailyReward(userRepository.dailyGift(token))) + } + + runCatching { + addAll(betRepository.getToConfirm(token).map { bet -> + ToConfirmBet( + betDetail = bet, + onConfirm = { + confirmBet( + response = it, + betId = bet.bet.id + ) + } + ) + }) + } + + runCatching { + addAll(betRepository.getWon(token).mapNotNull { result -> + currentUser.value?.let { + WonBet( + user = it, + betResult = result ) } - ) - }) - addAll(betRepository.getWon(token).mapNotNull { result -> - currentUser.value?.let { - WonBet( - user = it, - betResult = result - ) - } - }) + }) + } }.filter { it !in dismissedEvents } ) } @@ -90,17 +95,23 @@ class MainViewModel @Inject constructor( fun onLogout() { viewModelScope.launch { keystoreManager.deleteToken() - userRepository.currentUser.emit(null) - userRepository.userCoins.emit(null) + userRepository.resetCurrentUser() } } - private fun decreaseCoins(amount: Int) { + + fun increaseCoins(amount: Int) { viewModelScope.launch { - userRepository.userCoins.value?.let { - val newAmount = it - amount - userRepository.userCoins.emit(newAmount) + currentUser.value?.let { + userRepository.updateCurrentUserCoins(it.coins + amount) + } + } + } + private fun decreaseCoins(amount: Int) { + viewModelScope.launch { + currentUser.value?.let { + userRepository.updateCurrentUserCoins(it.coins - amount) } } } diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/main/event/DailyReward.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/main/event/DailyReward.kt index 3a6889b..fe86104 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/main/event/DailyReward.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/main/event/DailyReward.kt @@ -3,7 +3,7 @@ package fr.iut.alldev.allin.ui.main.event import androidx.compose.runtime.Composable import fr.iut.alldev.allin.ui.dailyReward.DailyRewardScreen -data class DailyReward(private val amount: Int) : AllInEvent() { +data class DailyReward(val amount: Int) : AllInEvent() { @Composable fun Display(onDismiss: () -> Unit) { DailyRewardScreen( diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/main/event/WonBet.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/main/event/WonBet.kt index ac7dbb1..5f8259e 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/main/event/WonBet.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/main/event/WonBet.kt @@ -8,7 +8,7 @@ import fr.iut.alldev.allin.ui.betResult.BetResultBottomSheet data class WonBet( private val user: User, - private val betResult: BetResultDetail, + val betResult: BetResultDetail, ) : AllInEvent() { @Composable fun Display( diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/navigation/NavHost.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/navigation/NavHost.kt index 984a0c8..0ea8aa3 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/navigation/NavHost.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/navigation/NavHost.kt @@ -137,7 +137,7 @@ internal fun AllInDrawerNavHost( route = Routes.BET_HISTORY ) { backHandlers() - BetHistoryScreen() + BetHistoryScreen(selectBet = selectBet) } composable( @@ -151,7 +151,7 @@ internal fun AllInDrawerNavHost( route = Routes.BET_CURRENT ) { backHandlers() - BetCurrentScreen() + BetCurrentScreen(selectBet = selectBet) } } } diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/preview/BetDetailPreviewProvider.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/preview/BetDetailPreviewProvider.kt index b108440..6a403f9 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/preview/BetDetailPreviewProvider.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/preview/BetDetailPreviewProvider.kt @@ -12,98 +12,101 @@ import fr.iut.alldev.allin.data.model.bet.vo.BetDetail class BetDetailPreviewProvider : PreviewParameterProvider<BetDetail> { override val values = BetWithStatusPreviewProvider().values.map { - BetDetail( - bet = it, - answers = when (it) { - is CustomBet -> listOf( - BetAnswerDetail( - response = "Answer 1", - totalStakes = 300, - totalParticipants = 8, - highestStake = 200, - odds = 1.0f - ), - BetAnswerDetail( - response = "Answer 2", - totalStakes = 300, - totalParticipants = 4, - highestStake = 200, - odds = 1.0f - ), - BetAnswerDetail( - response = "Answer 3", - totalStakes = 300, - totalParticipants = 2, - highestStake = 200, - odds = 1.0f - ), - BetAnswerDetail( - response = "Answer 4", - totalStakes = 300, - totalParticipants = 1, - highestStake = 200, - odds = 1.0f - ) + + val answers = when (it) { + is CustomBet -> listOf( + BetAnswerDetail( + response = "Answer 1", + totalStakes = 300, + totalParticipants = 8, + highestStake = 200, + odds = 1.0f + ), + BetAnswerDetail( + response = "Answer 2", + totalStakes = 300, + totalParticipants = 4, + highestStake = 200, + odds = 1.0f + ), + BetAnswerDetail( + response = "Answer 3", + totalStakes = 300, + totalParticipants = 2, + highestStake = 200, + odds = 1.0f + ), + BetAnswerDetail( + response = "Answer 4", + totalStakes = 300, + totalParticipants = 1, + highestStake = 200, + odds = 1.0f ) - is MatchBet -> listOf( - BetAnswerDetail( - response = "The Monarchs", - totalStakes = 300, - totalParticipants = 2, - highestStake = 200, - odds = 1.0f - ), - BetAnswerDetail( - response = "Climate Change", - totalStakes = 150, - totalParticipants = 1, - highestStake = 150, - odds = 2.0f - ) + ) + is MatchBet -> listOf( + BetAnswerDetail( + response = "The Monarchs", + totalStakes = 300, + totalParticipants = 2, + highestStake = 200, + odds = 1.0f + ), + BetAnswerDetail( + response = "Climate Change", + totalStakes = 150, + totalParticipants = 1, + highestStake = 150, + odds = 2.0f ) - is YesNoBet -> listOf( - BetAnswerDetail( - response = YES_VALUE, - totalStakes = 300, - totalParticipants = 2, - highestStake = 200, - odds = 1.0f - ), - BetAnswerDetail( - response = NO_VALUE, - totalStakes = 150, - totalParticipants = 1, - highestStake = 150, - odds = 2.0f - ) + ) + is YesNoBet -> listOf( + BetAnswerDetail( + response = YES_VALUE, + totalStakes = 300, + totalParticipants = 2, + highestStake = 200, + odds = 1.0f + ), + BetAnswerDetail( + response = NO_VALUE, + totalStakes = 150, + totalParticipants = 1, + highestStake = 150, + odds = 2.0f ) - }, + ) + } + + BetDetail( + bet = it, + answers = answers, participations = listOf( Participation( betId = it.id, username = "User1", - response = YES_VALUE, - stake = 200 - ), - Participation( - betId = it.id, - username = "User2", - response = YES_VALUE, + response = answers.first().response, stake = 100 ), Participation( betId = it.id, - username = "MyUser", - response = NO_VALUE, + username = "User 2", + response = answers.last().response, stake = 150 ) ), - userParticipation = null /*Participation( + userParticipation = Participation( + betId = it.id, + username = "User1", + response = answers.first().response, + stake = 100 + ), + wonParticipation = Participation( betId = it.id, - username = "MyUser", - response = NO_VALUE, - stake = 150 - )*/ + username = "User1", + response = answers.first().response, + stake = 100 + ) ) } } \ No newline at end of file diff --git a/src/data/src/dev/java/fr/iut/alldev/allin/data/api/MockAllInApi.kt b/src/data/src/dev/java/fr/iut/alldev/allin/data/api/MockAllInApi.kt index 04c189e..edb2a37 100644 --- a/src/data/src/dev/java/fr/iut/alldev/allin/data/api/MockAllInApi.kt +++ b/src/data/src/dev/java/fr/iut/alldev/allin/data/api/MockAllInApi.kt @@ -1,6 +1,7 @@ package fr.iut.alldev.allin.data.api import fr.iut.alldev.allin.data.api.interceptors.AllInAPIException +import fr.iut.alldev.allin.data.api.interceptors.AllInUnauthorizedException import fr.iut.alldev.allin.data.api.model.CheckUser import fr.iut.alldev.allin.data.api.model.RequestBet import fr.iut.alldev.allin.data.api.model.RequestParticipation @@ -8,6 +9,7 @@ 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.ResponseBetResult 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 @@ -15,12 +17,39 @@ 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 kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import java.time.Duration import java.time.ZonedDateTime import java.util.UUID class MockAllInApi : AllInApi { + init { + CoroutineScope(Dispatchers.Default).launch { + while (true) { + delay(10_000) + updateBets(ZonedDateTime.now()) + } + } + } + + private fun updateBets(date: ZonedDateTime) { + mockBets.forEachIndexed { idx, bet -> + if (bet.status != BetStatus.CANCELLED && bet.status != BetStatus.FINISHED) { + if (date >= bet.endRegistration) { + if (date >= bet.endBet) { + mockBets[idx] = bet.copy(status = BetStatus.CLOSING) + } else { + mockBets[idx] = bet.copy(status = BetStatus.IN_PROGRESS) + } + } + } + } + } + private fun getUserFromToken(token: String) = mockUsers.find { it.first.token == token.removePrefix("Bearer ") } @@ -79,6 +108,31 @@ class MockAllInApi : AllInApi { ) } + override suspend fun dailyGift(token: String): Int { + getUserFromToken(token) ?: throw AllInAPIException("Invalid login/password.") + if (mockGifts[token] == null) { + mockGifts[token] = ZonedDateTime.now().minusDays(1) + } + + val duration = Duration.between(mockGifts[token], ZonedDateTime.now()) + if (duration.toDays() >= 1) { + val amount = (10..150).random() + mockGifts[token] = ZonedDateTime.now() + + + mockUsers.replaceAll { + if (it.first.token == token) { + it.copy( + first = it.first.copy( + nbCoins = it.first.nbCoins + amount + ) + ) + } else it + } + return amount + } else throw AllInUnauthorizedException("Gift already taken today") + } + override suspend fun getAllBets(token: String): List<ResponseBet> { getUserFromToken(token) ?: throw AllInAPIException("Invalid login/password.") return mockBets @@ -105,7 +159,7 @@ class MockAllInApi : AllInApi { getUserFromToken(token) ?: throw AllInAPIException("Invalid login/password.") val bet = mockBets.find { it.id == id } ?: throw AllInAPIException("Unauthorized") mockResults.add( - BetResult( + ResponseBetResult( betId = id, result = value ) @@ -118,25 +172,72 @@ class MockAllInApi : AllInApi { val user = getUserFromToken(token) ?: throw AllInAPIException("Invalid login/password.") val betParticipations = mockParticipations.filter { it.betId == bet.id } val userParticipation = betParticipations.find { it.username == user.first.username } + val wonParticipation = if (bet.status == BetStatus.FINISHED) { + val result = mockResults.find { it.betId == bet.id } + betParticipations.filter { it.answer == result?.result }.maxByOrNull { it.stake } + } else null return ResponseBetDetail( bet = bet, answers = getAnswerDetails(bet, betParticipations), participations = betParticipations, - userParticipation = userParticipation + userParticipation = userParticipation, + wonParticipation = wonParticipation ) } override suspend fun getBetCurrent(token: String): List<ResponseBetDetail> { - return emptyList() + val user = getUserFromToken(token) ?: throw AllInAPIException("Invalid login/password.") + val betParticipations = mockParticipations.filter { it.username == user.first.username } + return betParticipations.map { p -> + getBet(token, p.betId) + }.filter { + it.bet.status !in listOf(BetStatus.FINISHED, BetStatus.CANCELLED) + } } override suspend fun getBetHistory(token: String): List<ResponseBetResultDetail> { - return emptyList() + val user = getUserFromToken(token) ?: throw AllInAPIException("Invalid login/password.") + val betParticipations = mockParticipations.filter { it.username == user.first.username } + return betParticipations.map { p -> + getBet(token, p.betId) + }.filter { + it.bet.status in listOf(BetStatus.FINISHED, BetStatus.CANCELLED) + }.mapNotNull { bet -> + val participation = bet.userParticipation ?: return@mapNotNull null + val result = mockResults.find { it.betId == bet.bet.id } ?: return@mapNotNull null + ResponseBetResultDetail( + betResult = result, + bet = bet.bet, + participation = participation, + amount = participation.stake, // TODO won amount + won = participation.answer == result.result + ) + } } override suspend fun getWon(token: String): List<ResponseBetResultDetail> { - return emptyList() + return getBetHistory(token).filter { + val isWon = it.won && mockWon[token]?.contains(it.bet.id ?: "") != true + if (isWon) { + mockWon[token]?.add(it.bet.id ?: "") ?: run { + mockWon[token] = mutableListOf(it.bet.id ?: "") + } + + mockUsers.replaceAll { user -> + if (user.first.token == token) { + user.copy( + first = user.first.copy( + nbCoins = user.first.nbCoins + it.amount + ) + ) + } else user + } + + true + } else false + + } } override suspend fun participateToBet(token: String, body: RequestParticipation) { @@ -340,7 +441,11 @@ class MockAllInApi : AllInApi { ) ) - private val mockResults by lazy { mutableListOf<BetResult>() } + private val mockResults by lazy { mutableListOf<ResponseBetResult>() } + + private val mockWon by lazy { mutableMapOf<String, MutableList<String>>() } + + private val mockGifts by lazy { mutableMapOf<String, ZonedDateTime>() } } } diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/api/AllInApi.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/api/AllInApi.kt index 1ade41b..5d941de 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/api/AllInApi.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/api/AllInApi.kt @@ -31,6 +31,9 @@ interface AllInApi { @POST("bets/add") suspend fun createBet(@Header("Authorization") token: String, @Body body: RequestBet) + @GET("users/gift") + suspend fun dailyGift(@Header("Authorization") token: String): Int + @GET("bets/gets") suspend fun getAllBets(@Header("Authorization") token: String): List<ResponseBet> diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/api/model/ApiBet.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/api/model/ApiBet.kt index 51bff76..0b2e1b2 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/api/model/ApiBet.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/api/model/ApiBet.kt @@ -115,15 +115,16 @@ data class ResponseBetDetail( val bet: ResponseBet, val answers: List<ResponseBetAnswerDetail>, val participations: List<ResponseParticipation>, - val userParticipation: ResponseParticipation? = null + val userParticipation: ResponseParticipation? = null, + val wonParticipation: ResponseParticipation? = null ) { fun toBetDetail() = BetDetail( bet = bet.toBet(), answers = answers.map { it.toAnswerDetail() }, participations = participations.map { it.toParticipation() }, - userParticipation = userParticipation?.toParticipation() - + userParticipation = userParticipation?.toParticipation(), + wonParticipation = wonParticipation?.toParticipation() ) } diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/vo/BetDetail.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/vo/BetDetail.kt index e629d48..408da42 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/vo/BetDetail.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/vo/BetDetail.kt @@ -7,7 +7,8 @@ data class BetDetail( val bet: Bet, val answers: List<BetAnswerDetail>, val participations: List<Participation>, - val userParticipation: Participation? + val userParticipation: Participation?, + val wonParticipation: Participation? ) { fun getAnswerOfResponse(response: String) = answers.find { it.response == response } diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/repository/UserRepository.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/repository/UserRepository.kt index c08036a..ef1f85d 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/repository/UserRepository.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/repository/UserRepository.kt @@ -2,15 +2,25 @@ package fr.iut.alldev.allin.data.repository import fr.iut.alldev.allin.data.model.User import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow abstract class UserRepository { - val currentUser by lazy { MutableStateFlow<User?>(null) } - val userCoins by lazy { MutableStateFlow<Int?>(null) } + internal val _currentUser by lazy { MutableStateFlow<User?>(null) } + val currentUser by lazy { _currentUser.asStateFlow() } - internal suspend fun updateUser(user: User) { - currentUser.emit(user) - userCoins.emit(user.coins) + suspend fun resetCurrentUser() { + _currentUser.emit(null) + } + + suspend fun updateCurrentUserCoins(value: Int) { + currentUser.value?.let { user -> + _currentUser.emit( + user.copy( + coins = value + ) + ) + } } abstract suspend fun login(username: String, password: String): String? @@ -18,4 +28,5 @@ abstract class UserRepository { abstract suspend fun login(token: String): String? abstract suspend fun register(username: String, email: String, password: String): String? + abstract suspend fun dailyGift(token: String): Int } \ No newline at end of file diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/repository/impl/BetRepositoryImpl.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/repository/impl/BetRepositoryImpl.kt index 7232099..cb6bc64 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/repository/impl/BetRepositoryImpl.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/repository/impl/BetRepositoryImpl.kt @@ -26,7 +26,7 @@ class BetRepositoryImpl @Inject constructor( } override suspend fun getCurrentBets(token: String): List<BetDetail> { - return api.getToConfirm(token.formatBearerToken()).map { + return api.getBetCurrent(token.formatBearerToken()).map { it.toBetDetail() } } @@ -54,7 +54,6 @@ class BetRepositoryImpl @Inject constructor( 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) } diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/repository/impl/UserRepositoryImpl.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/repository/impl/UserRepositoryImpl.kt index f0063d3..3a4e3ae 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/repository/impl/UserRepositoryImpl.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/repository/impl/UserRepositoryImpl.kt @@ -18,13 +18,13 @@ class UserRepositoryImpl @Inject constructor( password = password ) ) - updateUser(response.toUser()) + _currentUser.emit(response.toUser()) return response.token } override suspend fun login(token: String): String? { val response = api.login(token = token.formatBearerToken()) - updateUser(response.toUser()) + _currentUser.emit(response.toUser()) return response.token } @@ -37,7 +37,10 @@ class UserRepositoryImpl @Inject constructor( password = password ) ) - updateUser(response.toUser()) + _currentUser.emit(response.toUser()) return response.token } + + override suspend fun dailyGift(token: String): Int = + api.dailyGift(token) } \ No newline at end of file