Popular bet and refresh
continuous-integration/drone/push Build is passing Details

pull/5/head
avalin 11 months ago
parent b7491a1134
commit 515f6882c0

@ -1,138 +1,37 @@
package fr.iut.alldev.allin.ui.bet package fr.iut.alldev.allin.ui.bet
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
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.data.model.bet.Bet
import fr.iut.alldev.allin.data.model.bet.BetFilter import fr.iut.alldev.allin.ui.bet.components.BetScreenLoadedContent
import fr.iut.alldev.allin.ext.textId import fr.iut.alldev.allin.ui.core.AllInLoading
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.bet.components.BetScreenCard
import fr.iut.alldev.allin.ui.bet.components.BetScreenPopularCard
import fr.iut.alldev.allin.ui.core.AllInChip
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
@Composable @Composable
fun BetScreen( fun BetScreen(
viewModel: BetViewModel = hiltViewModel(), viewModel: BetViewModel = hiltViewModel(),
selectBet: (Bet, Boolean) -> Unit, selectBet: (Bet, Boolean) -> Unit,
) { ) {
val bets by viewModel.bets.collectAsStateWithLifecycle() val state by viewModel.state.collectAsStateWithLifecycle()
val filters by viewModel.filters.collectAsStateWithLifecycle() val filters by viewModel.filters.collectAsStateWithLifecycle()
val isRefreshing by viewModel.refreshing.collectAsStateWithLifecycle()
val refreshing by viewModel.isRefreshing.collectAsStateWithLifecycle() when (val s = state) {
val pullRefreshState = rememberPullRefreshState(refreshing, { viewModel.refresh() }) is BetViewModel.State.Loaded -> {
val progressAnimation by animateFloatAsState(pullRefreshState.progress * 15, label = "") BetScreenLoadedContent(
popularBet = s.popularBet,
LazyColumn( filters = filters,
modifier = Modifier bets = s.bets,
.pullRefresh(pullRefreshState) isRefreshing = isRefreshing,
.padding(top = with(LocalDensity.current) { selectBet = selectBet,
progressAnimation.toDp() toggleFilter = { viewModel.toggleFilter(it) },
}), refreshData = { viewModel.refreshData() }
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
) {
item {
Box(
Modifier.fillMaxWidth()
) {
BetScreenPopularCard(
modifier = Modifier
.padding(top = 13.dp, bottom = 10.dp)
.padding(horizontal = 13.dp),
nbPlayers = 12,
points = 2.35f,
pointUnit = "k",
title = "Emre va réussir son TP de CI/CD mercredi?"
)
PullRefreshIndicator(
modifier = Modifier
.align(Alignment.TopCenter),
refreshing = refreshing,
state = pullRefreshState
)
}
}
stickyHeader {
LazyRow(
modifier = Modifier
.background(
Brush.verticalGradient(
0.5f to AllInTheme.colors.mainSurface,
1f to Color.Transparent
)
)
.padding(top = 5.dp, bottom = 19.dp),
horizontalArrangement = Arrangement.spacedBy(9.dp),
contentPadding = PaddingValues(horizontal = 23.dp)
) {
items(BetFilter.entries) {
val isSelected by remember {
derivedStateOf {
filters.contains(it)
}
}
AllInChip(
text = stringResource(id = it.textId()),
isSelected = isSelected,
onClick = { viewModel.toggleFilter(it) }
)
}
}
}
itemsIndexed(
items = bets,
key = { _, it -> it.id }
) { idx, it ->
BetScreenCard(
creator = it.creator,
category = it.theme,
title = it.phrase,
date = it.endRegisterDate.formatToMediumDateNoYear(),
time = it.endRegisterDate.formatToTime(),
players = emptyList(), // TODO : Players
onClickParticipate = { selectBet(it, true) },
onClickCard = { selectBet(it, false) },
modifier = Modifier
.animateItemPlacement()
.padding(horizontal = 23.dp)
) )
if (idx != bets.lastIndex) { }
Spacer(modifier = Modifier.height(24.dp))
} BetViewModel.State.Loading -> {
AllInLoading(visible = true)
} }
} }
} }

@ -8,14 +8,13 @@ import fr.iut.alldev.allin.data.model.bet.BetFilter
import fr.iut.alldev.allin.data.repository.BetRepository import fr.iut.alldev.allin.data.repository.BetRepository
import fr.iut.alldev.allin.keystore.AllInKeystoreManager import fr.iut.alldev.allin.keystore.AllInKeystoreManager
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
@HiltViewModel @HiltViewModel
class BetViewModel @Inject constructor( class BetViewModel @Inject constructor(
@ -23,33 +22,23 @@ class BetViewModel @Inject constructor(
private val betRepository: BetRepository private val betRepository: BetRepository
) : ViewModel() { ) : ViewModel() {
private val _isRefreshing by lazy { MutableStateFlow(false) } private val _state: MutableStateFlow<State> by lazy { MutableStateFlow(State.Loading) }
val isRefreshing: StateFlow<Boolean> get() = _isRefreshing.asStateFlow() val state: StateFlow<State> by lazy { _state.asStateFlow() }
private val _bets: MutableStateFlow<List<Bet>> by lazy {
MutableStateFlow(emptyList())
}
val bets: StateFlow<List<Bet>> by lazy {
_bets.asStateFlow()
.filterNotNull()
.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5_000L),
emptyList()
)
}
private val _filters: MutableStateFlow<List<BetFilter>> by lazy {
MutableStateFlow(BetFilter.entries)
}
private val _filters: MutableStateFlow<List<BetFilter>> by lazy { MutableStateFlow(emptyList()) }
val filters get() = _filters.asStateFlow() val filters get() = _filters.asStateFlow()
private val _refreshing by lazy { MutableStateFlow(false) }
val refreshing by lazy { _refreshing.asStateFlow() }
init { init {
viewModelScope.launch { viewModelScope.launch {
refreshBets() refreshBets()
filters.collect { refreshBets() } filters
.debounce(1.seconds)
.collect {
refreshBets()
}
} }
} }
@ -67,10 +56,18 @@ class BetViewModel @Inject constructor(
private suspend fun refreshBets() { private suspend fun refreshBets() {
try { try {
_bets.emit( val token = keystoreManager.getTokenOrEmpty()
betRepository.getAllBets( _state.emit(
token = keystoreManager.getTokenOrEmpty(), State.Loaded(
filters = filters.value bets = betRepository.getAllBets(
token = token,
filters = filters.value
),
popularBet = try {
betRepository.getPopularBet(token)
} catch (e: Exception) {
null
}
) )
) )
} catch (e: Exception) { } catch (e: Exception) {
@ -78,12 +75,17 @@ class BetViewModel @Inject constructor(
} }
} }
fun refresh() { fun refreshData() {
viewModelScope.launch { viewModelScope.launch {
_isRefreshing.emit(true) _refreshing.emit(true)
refreshBets() refreshBets()
_isRefreshing.emit(false) _refreshing.emit(false)
} }
} }
sealed interface State {
data object Loading : State
data class Loaded(val bets: List<Bet>, val popularBet: Bet?) : State
}
} }

@ -36,14 +36,16 @@ fun BetScreenCard(
date: String, date: String,
time: String, time: String,
players: List<User>, players: List<User>,
modifier: Modifier = Modifier,
onClickParticipate: () -> Unit, onClickParticipate: () -> Unit,
onClickCard: () -> Unit, onClickCard: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true
) { ) {
AllInBouncyCard( AllInBouncyCard(
modifier = modifier.fillMaxWidth(), modifier = modifier.fillMaxWidth(),
radius = 16.dp, radius = 16.dp,
onClick = onClickCard onClick = onClickCard,
enabled = enabled
) { ) {
Column( Column(
Modifier.padding(horizontal = 19.dp, vertical = 11.dp) Modifier.padding(horizontal = 19.dp, vertical = 11.dp)
@ -71,8 +73,10 @@ fun BetScreenCard(
.padding(7.dp), .padding(7.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
BetProfilePictureRow(pictures = players.map { it.username to null }) if (players.isNotEmpty()) {
Spacer(modifier = Modifier.width(12.dp)) BetProfilePictureRow(pictures = players.map { it.username to null })
Spacer(modifier = Modifier.width(12.dp))
}
Text( Text(
text = pluralStringResource( text = pluralStringResource(
id = R.plurals.bet_players_waiting_format, id = R.plurals.bet_players_waiting_format,
@ -86,7 +90,8 @@ fun BetScreenCard(
RainbowButton( RainbowButton(
modifier = Modifier.padding(6.dp), modifier = Modifier.padding(6.dp),
text = stringResource(id = R.string.bet_participate), text = stringResource(id = R.string.bet_participate),
onClick = onClickParticipate onClick = onClickParticipate,
enabled = enabled
) )
} }
} }
@ -110,4 +115,41 @@ private fun BetScreenCardPreview() {
onClickCard = {} onClickCard = {}
) )
} }
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun BetScreenCardNoPlayersPreview() {
AllInTheme {
BetScreenCard(
creator = "Lucas",
category = "Études",
title = "Emre va réussir son TP de CI/CD mercredi?",
date = "12 Sept.",
time = "13:00",
players = emptyList(),
onClickParticipate = {},
onClickCard = {}
)
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun BetScreenCardDisabledPreview() {
AllInTheme {
BetScreenCard(
creator = "Lucas",
category = "Études",
title = "Emre va réussir son TP de CI/CD mercredi?",
date = "12 Sept.",
time = "13:00",
players = emptyList(),
onClickParticipate = {},
onClickCard = {},
enabled = false
)
}
} }

@ -0,0 +1,187 @@
package fr.iut.alldev.allin.ui.bet.components
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.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
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.data.model.bet.BetFilter
import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.data.model.bet.BinaryBet
import fr.iut.alldev.allin.ext.textId
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInChip
import java.time.ZonedDateTime
private const val DISABLED_OPACITY = .5f
@Composable
fun BetScreenLoadedContent(
popularBet: Bet?,
filters: List<BetFilter>,
bets: List<Bet>,
isRefreshing: Boolean,
selectBet: (Bet, Boolean) -> Unit,
toggleFilter: (BetFilter) -> Unit,
refreshData: () -> Unit
) {
val pullRefreshState = rememberPullRefreshState(isRefreshing, refreshData)
val progressAnimation by animateFloatAsState(pullRefreshState.progress * 15, label = "")
Box(Modifier.fillMaxSize()) {
LazyColumn(
modifier = Modifier
.pullRefresh(pullRefreshState)
.padding(top = with(LocalDensity.current) {
progressAnimation.toDp()
}),
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
) {
popularBet?.let {
item {
Box(Modifier.fillMaxWidth()) {
BetScreenPopularCard(
nbPlayers = 12, // Todo : Players
points = 2.35f, // Todo : Points
pointUnit = "k",
title = it.phrase,
onClick = { selectBet(it, false) },
enabled = !isRefreshing,
modifier = Modifier
.padding(top = 13.dp, bottom = 10.dp)
.padding(horizontal = 13.dp)
.let {
if (isRefreshing) it.alpha(DISABLED_OPACITY)
else it
}
)
}
}
}
stickyHeader {
LazyRow(
modifier = Modifier
.background(
Brush.verticalGradient(
0.5f to AllInTheme.colors.mainSurface,
1f to Color.Transparent
)
)
.zIndex(1f),
horizontalArrangement = Arrangement.spacedBy(9.dp),
contentPadding = PaddingValues(horizontal = 23.dp)
) {
items(BetFilter.entries) {
val isSelected by remember(filters) {
derivedStateOf {
filters.contains(it)
}
}
AllInChip(
text = stringResource(id = it.textId()),
isSelected = isSelected,
onClick = { toggleFilter(it) },
enabled = !isRefreshing,
modifier = Modifier.let {
if (isRefreshing) it.alpha(DISABLED_OPACITY)
else it
}
)
}
}
}
itemsIndexed(
items = bets,
key = { _, it -> it.id }
) { idx, it ->
BetScreenCard(
creator = it.creator,
category = it.theme,
title = it.phrase,
date = it.endRegisterDate.formatToMediumDateNoYear(),
time = it.endRegisterDate.formatToTime(),
players = emptyList(), // TODO : Players
onClickParticipate = { selectBet(it, true) },
onClickCard = { selectBet(it, false) },
enabled = !isRefreshing,
modifier = Modifier
.animateItemPlacement()
.padding(horizontal = 23.dp)
.let {
if (isRefreshing) it.alpha(DISABLED_OPACITY)
else it
}
)
if (idx != bets.lastIndex) {
Spacer(modifier = Modifier.height(24.dp))
}
}
}
PullRefreshIndicator(
modifier = Modifier.align(Alignment.TopCenter),
refreshing = isRefreshing,
state = pullRefreshState
)
}
}
@Preview
@Composable
private fun BetScreenLoadedContentPreview() {
AllInTheme {
BetScreenLoadedContent(
popularBet = BinaryBet(
id = "Arleen",
creator = "Omar",
theme = "Kyli",
phrase = "Leigha",
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = false,
betStatus = BetStatus.IN_PROGRESS
),
filters = emptyList(),
bets = emptyList(),
isRefreshing = true,
selectBet = { _, _ -> },
toggleFilter = { },
refreshData = { }
)
}
}

@ -26,7 +26,7 @@ import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.ext.shadow import fr.iut.alldev.allin.ext.shadow
import fr.iut.alldev.allin.theme.AllInColorToken import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInCard import fr.iut.alldev.allin.ui.core.AllInBouncyCard
import fr.iut.alldev.allin.ui.core.HighlightedText import fr.iut.alldev.allin.ui.core.HighlightedText
import kotlin.math.ceil import kotlin.math.ceil
@ -36,9 +36,11 @@ fun BetScreenPopularCard(
points: Float, points: Float,
pointUnit: String, pointUnit: String,
title: String, title: String,
onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
enabled: Boolean = true
) { ) {
AllInCard( AllInBouncyCard(
modifier = modifier modifier = modifier
.let { .let {
if (isSystemInDarkTheme()) { if (isSystemInDarkTheme()) {
@ -63,7 +65,9 @@ fun BetScreenPopularCard(
.fillMaxWidth(), .fillMaxWidth(),
backgroundColor = AllInColorToken.allInDark, backgroundColor = AllInColorToken.allInDark,
borderWidth = 2.dp, borderWidth = 2.dp,
borderBrush = AllInColorToken.allInMainGradient borderBrush = AllInColorToken.allInMainGradient,
onClick = onClick,
enabled = enabled
) { ) {
Column(modifier = Modifier.padding(13.dp)) { Column(modifier = Modifier.padding(13.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
@ -143,7 +147,8 @@ private fun BetScreenPopularCardPreview() {
nbPlayers = 12, nbPlayers = 12,
points = 2.35f, points = 2.35f,
pointUnit = "k", pointUnit = "k",
title = "Emre va réussir son TP de CI/CD mercredi?" title = "Emre va réussir son TP de CI/CD mercredi?",
onClick = {}
) )
} }
} }
@ -156,7 +161,8 @@ private fun BetScreenPopularCardSingularPreview() {
nbPlayers = 1, nbPlayers = 1,
points = 1.0f, points = 1.0f,
pointUnit = "", pointUnit = "",
title = "Emre va réussir son TP de CI/CD mercredi?" title = "Emre va réussir son TP de CI/CD mercredi?",
onClick = {}
) )
} }
} }

@ -121,11 +121,13 @@ fun AllInBouncyCard(
AllInCard( AllInCard(
modifier = modifier modifier = modifier
.combinedClickable( .let {
interactionSource = interactionSource, if (enabled) it.combinedClickable(
indication = null, interactionSource = interactionSource,
onClick = { onClick?.let { it() } } indication = null,
) onClick = { onClick?.let { it() } }
) else it
}
.scale(scale), .scale(scale),
onClick = null, onClick = null,
radius = radius, radius = radius,

@ -2,8 +2,11 @@ package fr.iut.alldev.allin.ui.core
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Box
import androidx.compose.material3.* import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -17,12 +20,12 @@ import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AllInChip( fun AllInChip(
text: String, text: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
isSelected: Boolean = false, isSelected: Boolean = false,
enabled: Boolean = true,
onClick: () -> Unit = {}, onClick: () -> Unit = {},
radius: Dp = 50.dp, radius: Dp = 50.dp,
selectedColor: Color = AllInColorToken.allInPurple, selectedColor: Color = AllInColorToken.allInPurple,
@ -34,10 +37,12 @@ fun AllInChip(
onClick = onClick, onClick = onClick,
border = if (!isSelected) BorderStroke(1.dp, AllInTheme.colors.border) else null, border = if (!isSelected) BorderStroke(1.dp, AllInTheme.colors.border) else null,
colors = CardDefaults.cardColors( colors = CardDefaults.cardColors(
containerColor = if (isSelected) selectedColor else unselectedColor containerColor = if (isSelected) selectedColor else unselectedColor,
) disabledContainerColor = if (isSelected) selectedColor else unselectedColor
),
enabled = enabled
) { ) {
Box { Box{
Text( Text(
text = text, text = text,
modifier = Modifier modifier = Modifier

@ -34,13 +34,9 @@ import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.window.DialogWindowProvider
import fr.iut.alldev.allin.theme.AllInColorToken import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
import kotlin.math.PI import kotlin.math.PI
@ -59,34 +55,23 @@ fun AllInLoading(
enter = fadeIn(), enter = fadeIn(),
exit = fadeOut() exit = fadeOut()
) { ) {
Dialog( Box(
onDismissRequest = {}, modifier = modifier
properties = DialogProperties( .fillMaxSize()
dismissOnBackPress = false, .clickable(
dismissOnClickOutside = false, interactionSource = interactionSource,
decorFitsSystemWindows = false, indication = null,
usePlatformDefaultWidth = false onClick = {}
)
) {
(LocalView.current.parent as DialogWindowProvider).window.setDimAmount(0f)
Box(
modifier = modifier
.fillMaxSize()
.clickable(
interactionSource = interactionSource,
indication = null,
onClick = {}
)
.background(AllInTheme.colors.mainSurface.copy(alpha = .4f))
) {
AllInCircularProgressIndicator(
modifier = Modifier
.align(Alignment.Center)
.size(50.dp),
brush = brush,
strokeWidth = 7.dp
) )
} .background(AllInTheme.colors.mainSurface.copy(alpha = .4f))
) {
AllInCircularProgressIndicator(
modifier = Modifier
.align(Alignment.Center)
.size(50.dp),
brush = brush,
strokeWidth = 7.dp
)
} }
} }
} }
@ -112,7 +97,7 @@ fun AllInCircularProgressIndicator(
durationMillis = RotationDuration * RotationsPerCycle, durationMillis = RotationDuration * RotationsPerCycle,
easing = LinearEasing easing = LinearEasing
) )
) ), label = ""
) )
val baseRotation = transition.animateFloat( val baseRotation = transition.animateFloat(
0f, 0f,

@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
@HiltViewModel @HiltViewModel
class FriendsScreenViewModel @Inject constructor( class FriendsScreenViewModel @Inject constructor(
@ -40,7 +41,7 @@ class FriendsScreenViewModel @Inject constructor(
} }
_search _search
.debounce(1_000L) .debounce(1.seconds)
.collect { itSearch -> .collect { itSearch ->
try { try {
_state.emit( _state.emit(

@ -6,6 +6,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import fr.iut.alldev.allin.data.model.FriendStatus import fr.iut.alldev.allin.data.model.FriendStatus
import fr.iut.alldev.allin.data.model.User import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.data.repository.FriendRepository import fr.iut.alldev.allin.data.repository.FriendRepository
import fr.iut.alldev.allin.data.repository.UserRepository
import fr.iut.alldev.allin.keystore.AllInKeystoreManager import fr.iut.alldev.allin.keystore.AllInKeystoreManager
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@ -16,6 +17,7 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class RankingViewModel @Inject constructor( class RankingViewModel @Inject constructor(
private val friendRepository: FriendRepository, private val friendRepository: FriendRepository,
private val userRepository: UserRepository,
private val keystoreManager: AllInKeystoreManager private val keystoreManager: AllInKeystoreManager
) : ViewModel() { ) : ViewModel() {
private val _state by lazy { MutableStateFlow<State>(State.Loading) } private val _state by lazy { MutableStateFlow<State>(State.Loading) }
@ -26,9 +28,17 @@ class RankingViewModel @Inject constructor(
try { try {
_state.emit( _state.emit(
State.Loaded( State.Loaded(
friends = friendRepository.getFriends( friends = buildList {
token = keystoreManager.getTokenOrEmpty() addAll(
).filter { it.friendStatus == FriendStatus.FRIEND } friendRepository.getFriends(
token = keystoreManager.getTokenOrEmpty()
).filter { it.friendStatus == FriendStatus.FRIEND }
)
userRepository.currentUserState.value?.let {
add(it)
}
}
) )
) )
} catch (e: Exception) { } catch (e: Exception) {

@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
@HiltViewModel @HiltViewModel
class SplashScreenViewModel @Inject constructor( class SplashScreenViewModel @Inject constructor(
@ -20,7 +21,7 @@ class SplashScreenViewModel @Inject constructor(
) : ViewModel() { ) : ViewModel() {
val state: StateFlow<State> by lazy { val state: StateFlow<State> by lazy {
flow { flow {
delay(1_000L) delay(1.seconds)
keystoreManager.getToken()?.let { token -> keystoreManager.getToken()?.let { token ->
runCatching { runCatching {

@ -78,6 +78,9 @@ interface AllInApi {
@Body body: RequestBetFilters @Body body: RequestBetFilters
): List<ResponseBet> ): List<ResponseBet>
@GET("bets/popular")
suspend fun getPopularBet(@Header("Authorization") token: String): ResponseBet?
@GET("bets/toConfirm") @GET("bets/toConfirm")
suspend fun getToConfirm(@Header("Authorization") token: String): List<ResponseBetDetail> suspend fun getToConfirm(@Header("Authorization") token: String): List<ResponseBetDetail>

@ -47,7 +47,7 @@ class MockAllInApi : AllInApi {
if (date >= bet.endBet) { if (date >= bet.endBet) {
mockBets[idx] = bet.copy(status = BetStatus.CLOSING) mockBets[idx] = bet.copy(status = BetStatus.CLOSING)
} else { } else {
mockBets[idx] = bet.copy(status = BetStatus.IN_PROGRESS) mockBets[idx] = bet.copy(status = BetStatus.WAITING)
} }
} }
} }
@ -120,7 +120,7 @@ class MockAllInApi : AllInApi {
isPrivate = body.isPrivate, isPrivate = body.isPrivate,
response = body.response, response = body.response,
type = BetType.BINARY, type = BetType.BINARY,
status = BetStatus.WAITING, status = BetStatus.IN_PROGRESS,
createdBy = getUserFromToken(token)?.first?.username ?: "" createdBy = getUserFromToken(token)?.first?.username ?: ""
) )
) )
@ -225,6 +225,10 @@ class MockAllInApi : AllInApi {
} }
} }
override suspend fun getPopularBet(token: String): ResponseBet? {
return mockBets.firstOrNull()
}
override suspend fun getToConfirm(token: String): List<ResponseBetDetail> { override suspend fun getToConfirm(token: String): List<ResponseBetDetail> {
val user = getUserFromToken(token) ?: throw MockAllInApiException("Invalid login/password.") val user = getUserFromToken(token) ?: throw MockAllInApiException("Invalid login/password.")
return mockBets.filter { return mockBets.filter {

@ -13,6 +13,7 @@ abstract class BetRepository {
abstract suspend fun getBet(id: String, token: String): BetDetail abstract suspend fun getBet(id: String, token: String): BetDetail
abstract suspend fun participateToBet(participation: Participation, token: String) abstract suspend fun participateToBet(participation: Participation, token: String)
abstract suspend fun getAllBets(token: String, filters: List<BetFilter>): List<Bet> abstract suspend fun getAllBets(token: String, filters: List<BetFilter>): List<Bet>
abstract suspend fun getPopularBet(token: String): Bet?
abstract suspend fun confirmBet(token: String, id: String, response: String) abstract suspend fun confirmBet(token: String, id: String, response: String)
abstract suspend fun getToConfirm(token: String): List<BetDetail> abstract suspend fun getToConfirm(token: String): List<BetDetail>
abstract suspend fun getWon(token: String): List<BetResultDetail> abstract suspend fun getWon(token: String): List<BetResultDetail>

@ -54,6 +54,9 @@ class BetRepositoryImpl @Inject constructor(
RequestBetFilters(filters) RequestBetFilters(filters)
).map { it.toBet() } ).map { it.toBet() }
override suspend fun getPopularBet(token: String): Bet? =
api.getPopularBet(token.formatBearerToken())?.toBet()
override suspend fun getToConfirm(token: String): List<BetDetail> = override suspend fun getToConfirm(token: String): List<BetDetail> =
api.getToConfirm(token.formatBearerToken()).map { it.toBetDetail() } api.getToConfirm(token.formatBearerToken()).map { it.toBetDetail() }

Loading…
Cancel
Save