Friends Screen + fix string resources format
continuous-integration/drone/push Build is passing Details

pull/5/head
avalin 8 months ago
parent e29c5d7152
commit 9d7061fa62

@ -5,10 +5,11 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithTag
import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.HiltAndroidTest
import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.test.TestTags import fr.iut.alldev.allin.test.TestTags
import fr.iut.alldev.allin.test.mock.Bets import fr.iut.alldev.allin.test.mock.Bets
import fr.iut.alldev.allin.ui.MainActivity
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.MainActivity
import fr.iut.alldev.allin.vo.bet.displayer.BetTestDisplayer import fr.iut.alldev.allin.vo.bet.displayer.BetTestDisplayer
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
@ -33,13 +34,22 @@ class BetVOTest {
} }
@Test @Test
fun testDisplayer_shouldDisplayYesNoBetUI(){ fun testDisplayer_shouldDisplayYesNoBetUI() {
//Given //Given
val currentUser = User(
id = "1",
username = "test",
email = "test@test.fr",
coins = 120
)
//When //When
composeTestRule.activity.setContent { composeTestRule.activity.setContent {
AllInTheme{ AllInTheme {
displayer.DisplayBet(Bets.bets[0]) displayer.DisplayBet(
Bets.bets[0],
currentUser
)
} }
} }
//Expect //Expect
@ -49,13 +59,22 @@ class BetVOTest {
} }
@Test @Test
fun testDisplayer_shouldDisplayMatchUI(){ fun testDisplayer_shouldDisplayMatchUI() {
//Given //Given
val currentUser = User(
id = "1",
username = "test",
email = "test@test.fr",
coins = 120
)
//When //When
composeTestRule.activity.setContent { composeTestRule.activity.setContent {
AllInTheme{ AllInTheme {
displayer.DisplayBet(Bets.bets[1]) displayer.DisplayBet(
Bets.bets[1],
currentUser
)
} }
} }
//Expect //Expect
@ -65,13 +84,22 @@ class BetVOTest {
} }
@Test @Test
fun testDisplayer_shouldDisplayCustomBetUI(){ fun testDisplayer_shouldDisplayCustomBetUI() {
//Given //Given
val currentUser = User(
id = "1",
username = "test",
email = "test@test.fr",
coins = 120
)
//When //When
composeTestRule.activity.setContent { composeTestRule.activity.setContent {
AllInTheme{ AllInTheme {
displayer.DisplayBet(Bets.bets[2]) displayer.DisplayBet(
Bets.bets[2],
currentUser
)
} }
} }
//Expect //Expect

@ -4,23 +4,24 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
import fr.iut.alldev.allin.test.TestTags import fr.iut.alldev.allin.test.TestTags
import fr.iut.alldev.allin.vo.bet.BetDisplayer import fr.iut.alldev.allin.vo.bet.BetDisplayer
class BetTestDisplayer : BetDisplayer { class BetTestDisplayer : BetDisplayer {
@Composable @Composable
override fun DisplayYesNoBet(betDetail: BetDetail) { override fun DisplayBinaryBet(betDetail: BetDetail, currentUser: User) {
Text("This is a YesNo Bet", Modifier.testTag(TestTags.YES_NO_BET.tag)) Text("This is a Binary Bet", Modifier.testTag(TestTags.YES_NO_BET.tag))
} }
@Composable @Composable
override fun DisplayMatchBet(betDetail: BetDetail) { override fun DisplayMatchBet(betDetail: BetDetail, currentUser: User) {
Text("This is a Match Bet", Modifier.testTag(TestTags.MATCH_BET.tag)) Text("This is a Match Bet", Modifier.testTag(TestTags.MATCH_BET.tag))
} }
@Composable @Composable
override fun DisplayCustomBet(betDetail: BetDetail) { override fun DisplayCustomBet(betDetail: BetDetail, currentUser: User) {
Text("This is a Custom Bet", Modifier.testTag(TestTags.CUSTOM_BET.tag)) Text("This is a Custom Bet", Modifier.testTag(TestTags.CUSTOM_BET.tag))
} }
} }

@ -18,4 +18,10 @@ fun String.verifyIsFloat(locale: Locale): String? {
fun String.toFloatOrNull(locale: Locale): Float? { fun String.toFloatOrNull(locale: Locale): Float? {
val format = DecimalFormat("0.##", DecimalFormatSymbols.getInstance(locale)) val format = DecimalFormat("0.##", DecimalFormatSymbols.getInstance(locale))
return format.parse(this)?.toFloat() return format.parse(this)?.toFloat()
}
fun String.asFallbackProfileUsername() = buildString {
this@asFallbackProfileUsername.trim().split("\\s+".toRegex(), limit = 2).forEach {
append(it.first().uppercase())
}
} }

@ -22,7 +22,6 @@ import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -34,6 +33,7 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import fr.iut.alldev.allin.data.ext.formatToMediumDateNoYear import fr.iut.alldev.allin.data.ext.formatToMediumDateNoYear
import fr.iut.alldev.allin.data.ext.formatToTime 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
@ -50,10 +50,10 @@ fun BetScreen(
viewModel: BetViewModel = hiltViewModel(), viewModel: BetViewModel = hiltViewModel(),
selectBet: (Bet, Boolean) -> Unit, selectBet: (Bet, Boolean) -> Unit,
) { ) {
val bets by viewModel.bets.collectAsState() val bets by viewModel.bets.collectAsStateWithLifecycle()
val filters by viewModel.filters.collectAsState() val filters by viewModel.filters.collectAsStateWithLifecycle()
val refreshing by viewModel.isRefreshing.collectAsState() val refreshing by viewModel.isRefreshing.collectAsStateWithLifecycle()
val pullRefreshState = rememberPullRefreshState(refreshing, { viewModel.refresh() }) val pullRefreshState = rememberPullRefreshState(refreshing, { viewModel.refresh() })
val progressAnimation by animateFloatAsState(pullRefreshState.progress * 15, label = "") val progressAnimation by animateFloatAsState(pullRefreshState.progress * 15, label = "")
@ -123,7 +123,7 @@ fun BetScreen(
title = it.phrase, title = it.phrase,
date = it.endRegisterDate.formatToMediumDateNoYear(), date = it.endRegisterDate.formatToMediumDateNoYear(),
time = it.endRegisterDate.formatToTime(), time = it.endRegisterDate.formatToTime(),
players = List(3) { null }, players = emptyList(), // TODO : Players
onClickParticipate = { selectBet(it, true) }, onClickParticipate = { selectBet(it, true) },
onClickCard = { selectBet(it, false) }, onClickCard = { selectBet(it, false) },
modifier = Modifier modifier = Modifier

@ -24,8 +24,7 @@ class BetViewModel @Inject constructor(
) : ViewModel() { ) : ViewModel() {
private val _isRefreshing by lazy { MutableStateFlow(false) } private val _isRefreshing by lazy { MutableStateFlow(false) }
val isRefreshing: StateFlow<Boolean> val isRefreshing: StateFlow<Boolean> get() = _isRefreshing.asStateFlow()
get() = _isRefreshing.asStateFlow()
private val _bets: MutableStateFlow<List<Bet>> by lazy { private val _bets: MutableStateFlow<List<Bet>> by lazy {
MutableStateFlow(emptyList()) MutableStateFlow(emptyList())
@ -45,7 +44,7 @@ class BetViewModel @Inject constructor(
MutableStateFlow(BetFilter.entries) MutableStateFlow(BetFilter.entries)
} }
val filters by lazy { _filters.asStateFlow() } val filters get() = _filters.asStateFlow()
init { init {
viewModelScope.launch { viewModelScope.launch {

@ -15,12 +15,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
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 fr.iut.alldev.allin.R import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInBouncyCard import fr.iut.alldev.allin.ui.core.AllInBouncyCard
import fr.iut.alldev.allin.ui.core.RainbowButton import fr.iut.alldev.allin.ui.core.RainbowButton
@ -35,7 +35,7 @@ fun BetScreenCard(
title: String, title: String,
date: String, date: String,
time: String, time: String,
players: List<Painter?>, players: List<User>,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClickParticipate: () -> Unit, onClickParticipate: () -> Unit,
onClickCard: () -> Unit, onClickCard: () -> Unit,
@ -71,7 +71,7 @@ fun BetScreenCard(
.padding(7.dp), .padding(7.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
BetProfilePictureRow(pictures = players) BetProfilePictureRow(pictures = players.map { it.username to null })
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.width(12.dp))
Text( Text(
text = pluralStringResource( text = pluralStringResource(
@ -103,7 +103,9 @@ private fun BetScreenCardPreview() {
title = "Emre va réussir son TP de CI/CD mercredi?", title = "Emre va réussir son TP de CI/CD mercredi?",
date = "12 Sept.", date = "12 Sept.",
time = "13:00", time = "13:00",
players = List(3) { null }, players = listOf(
User(id = "", username = "Lucas D", email = "", coins = 0),
),
onClickParticipate = {}, onClickParticipate = {},
onClickCard = {} onClickCard = {}
) )

@ -48,7 +48,7 @@ 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.MatchBet
import fr.iut.alldev.allin.data.model.bet.NO_VALUE 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.YES_VALUE
import fr.iut.alldev.allin.data.model.bet.YesNoBet import fr.iut.alldev.allin.data.model.bet.BinaryBet
import fr.iut.alldev.allin.data.model.bet.vo.BetAnswerDetail import fr.iut.alldev.allin.data.model.bet.vo.BetAnswerDetail
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
import fr.iut.alldev.allin.ext.formatToSimple import fr.iut.alldev.allin.ext.formatToSimple
@ -153,7 +153,7 @@ fun ConfirmationAnswers(
when (val bet = betDetail.bet) { when (val bet = betDetail.bet) {
is CustomBet -> bet.possibleAnswers is CustomBet -> bet.possibleAnswers
is MatchBet -> listOf(bet.nameTeam1, bet.nameTeam2) is MatchBet -> listOf(bet.nameTeam1, bet.nameTeam2)
is YesNoBet -> listOf(YES_VALUE, NO_VALUE) is BinaryBet -> listOf(YES_VALUE, NO_VALUE)
} }
} }
@ -295,7 +295,7 @@ private fun BetConfirmationBottomSheetContentPreview() {
AllInTheme { AllInTheme {
BetConfirmationBottomSheetContent( BetConfirmationBottomSheetContent(
betDetail = BetDetail( betDetail = BetDetail(
bet = YesNoBet( bet = BinaryBet(
id = "1", id = "1",
theme = "Theme", theme = "Theme",
phrase = "Phrase", phrase = "Phrase",

@ -96,7 +96,7 @@ class BetCreationViewModel @Inject constructor(
if (!hasError.value) { if (!hasError.value) {
try { try {
userRepository.currentUser.value?.let { currentUser -> userRepository.currentUserState.value?.let { currentUser ->
val bet = BetFactory.createBet( val bet = BetFactory.createBet(
id = "", id = "",
betType = selectedBetType.value, betType = selectedBetType.value,

@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text 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
@ -16,6 +15,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.ext.asFallbackProfileUsername
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.AllInCoinCount import fr.iut.alldev.allin.ui.core.AllInCoinCount
@ -45,7 +45,10 @@ fun BetCreationScreenFriendLine(
checked = isSelected, checked = isSelected,
modifier = Modifier.padding(end = 7.dp) modifier = Modifier.padding(end = 7.dp)
) )
ProfilePicture(modifier = Modifier.size(25.dp)) ProfilePicture(
fallback = username.asFallbackProfileUsername(),
size = 25.dp
)
Text( Text(
text = username, text = username,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,

@ -1,10 +1,10 @@
package fr.iut.alldev.allin.ui.betHistory package fr.iut.alldev.allin.ui.betHistory
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import fr.iut.alldev.allin.R import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.ext.formatToMediumDateNoYear import fr.iut.alldev.allin.data.ext.formatToMediumDateNoYear
import fr.iut.alldev.allin.data.ext.formatToTime import fr.iut.alldev.allin.data.ext.formatToTime
@ -16,7 +16,7 @@ fun BetCurrentScreen(
selectBet: (Bet, Boolean) -> Unit, selectBet: (Bet, Boolean) -> Unit,
viewModel: BetCurrentViewModel = hiltViewModel() viewModel: BetCurrentViewModel = hiltViewModel()
) { ) {
val bets by viewModel.bets.collectAsState() val bets by viewModel.bets.collectAsStateWithLifecycle()
GenericHistory( GenericHistory(
title = stringResource(id = R.string.bet_history_current_title), title = stringResource(id = R.string.bet_history_current_title),
bets = bets, bets = bets,

@ -1,10 +1,10 @@
package fr.iut.alldev.allin.ui.betHistory package fr.iut.alldev.allin.ui.betHistory
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import fr.iut.alldev.allin.R import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.ext.formatToMediumDateNoYear import fr.iut.alldev.allin.data.ext.formatToMediumDateNoYear
import fr.iut.alldev.allin.data.ext.formatToTime import fr.iut.alldev.allin.data.ext.formatToTime
@ -16,7 +16,7 @@ fun BetHistoryScreen(
selectBet: (Bet, Boolean) -> Unit, selectBet: (Bet, Boolean) -> Unit,
viewModel: BetHistoryViewModel = hiltViewModel() viewModel: BetHistoryViewModel = hiltViewModel()
) { ) {
val bets by viewModel.bets.collectAsState() val bets by viewModel.bets.collectAsStateWithLifecycle()
GenericHistory( GenericHistory(
title = stringResource(id = R.string.bet_history_title), title = stringResource(id = R.string.bet_history_title),
bets = bets, bets = bets,

@ -21,7 +21,7 @@ import fr.iut.alldev.allin.data.ext.formatToMediumDateNoYear
import fr.iut.alldev.allin.data.ext.formatToTime 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.BetStatus import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.data.model.bet.YesNoBet import fr.iut.alldev.allin.data.model.bet.BinaryBet
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.betResult.components.BetResultBottomSheetBetCard import fr.iut.alldev.allin.ui.betResult.components.BetResultBottomSheetBetCard
@ -133,7 +133,7 @@ private fun BetResultBottomSheetContentPreview() {
BetResultBottomSheetContent( BetResultBottomSheetContent(
username = "Pseudo", username = "Pseudo",
coinAmount = 3976, coinAmount = 3976,
bet = YesNoBet( bet = BinaryBet(
id = "1", id = "1",
theme = "Theme", theme = "Theme",
phrase = "Phrase", phrase = "Phrase",

@ -25,7 +25,7 @@ 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.MatchBet
import fr.iut.alldev.allin.data.model.bet.NO_VALUE 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.YES_VALUE
import fr.iut.alldev.allin.data.model.bet.YesNoBet import fr.iut.alldev.allin.data.model.bet.BinaryBet
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
import fr.iut.alldev.allin.ext.formatToSimple import fr.iut.alldev.allin.ext.formatToSimple
import fr.iut.alldev.allin.theme.AllInColorToken import fr.iut.alldev.allin.theme.AllInColorToken
@ -77,7 +77,7 @@ fun BetDetail.getParticipationAnswers(): List<@Composable RowScope.() -> Unit> {
} }
} }
is YesNoBet -> buildList { is BinaryBet -> buildList {
add { add {
this@getParticipationAnswers.getAnswerOfResponse(YES_VALUE)?.let { this@getParticipationAnswers.getAnswerOfResponse(YES_VALUE)?.let {
ParticipationAnswerLine( ParticipationAnswerLine(
@ -144,7 +144,7 @@ fun Bet.getAnswerFromParticipationIdx(idx: Int) =
else -> "" else -> ""
} }
is YesNoBet -> when (idx) { is BinaryBet -> when (idx) {
0 -> YES_VALUE 0 -> YES_VALUE
1 -> NO_VALUE 1 -> NO_VALUE
else -> "" else -> ""

@ -55,6 +55,7 @@ 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.YES_VALUE
import fr.iut.alldev.allin.data.model.bet.vo.BetAnswerDetail import fr.iut.alldev.allin.data.model.bet.vo.BetAnswerDetail
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
import fr.iut.alldev.allin.ext.asFallbackProfileUsername
import fr.iut.alldev.allin.ext.asPaddingValues import fr.iut.alldev.allin.ext.asPaddingValues
import fr.iut.alldev.allin.ext.bottomSheetNavigationBarsInsets import fr.iut.alldev.allin.ext.bottomSheetNavigationBarsInsets
import fr.iut.alldev.allin.ext.formatToSimple import fr.iut.alldev.allin.ext.formatToSimple
@ -295,7 +296,7 @@ class BetStatusBottomSheetBetDisplayer(
} }
@Composable @Composable
override fun DisplayYesNoBet(betDetail: BetDetail, currentUser: User) { override fun DisplayBinaryBet(betDetail: BetDetail, currentUser: User) {
DisplayBetDail( DisplayBetDail(
betDetail = betDetail, betDetail = betDetail,
currentUser = currentUser, currentUser = currentUser,
@ -390,7 +391,10 @@ fun BetStatusParticipant(
horizontalArrangement = Arrangement.spacedBy(7.dp), horizontalArrangement = Arrangement.spacedBy(7.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
ProfilePicture(modifier = Modifier.size(25.dp)) ProfilePicture(
fallback = username.asFallbackProfileUsername(),
size = 25.dp
)
Text( Text(
text = username, text = username,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,

@ -6,17 +6,17 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PhotoCamera
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon 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
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
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
@ -26,6 +26,7 @@ import fr.iut.alldev.allin.theme.AllInTheme
@Composable @Composable
fun ProfilePicture( fun ProfilePicture(
fallback: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
image: Painter? = null, image: Painter? = null,
borderWidth: Dp? = null, borderWidth: Dp? = null,
@ -49,13 +50,14 @@ fun ProfilePicture(
.clip(shape) .clip(shape)
) )
} ?: run { } ?: run {
Icon( Text(
imageVector = Icons.Default.PhotoCamera, text = fallback,
tint = AllInColorToken.white, style = AllInTheme.typography.p2,
contentDescription = null, textAlign = TextAlign.Center,
fontSize = with(LocalDensity.current) { (size / 2).toSp() },
color = AllInColorToken.white,
modifier = Modifier modifier = Modifier
.align(Alignment.Center) .align(Alignment.Center)
.fillMaxSize(0.5f)
.clip(shape) .clip(shape)
) )
} }
@ -68,7 +70,7 @@ fun ProfilePicture(
@Composable @Composable
private fun ProfilePictureDefaultPreview() { private fun ProfilePictureDefaultPreview() {
AllInTheme { AllInTheme {
ProfilePicture() ProfilePicture("LS")
} }
} }
@ -77,6 +79,7 @@ private fun ProfilePictureDefaultPreview() {
private fun ProfilePicturePreview() { private fun ProfilePicturePreview() {
AllInTheme { AllInTheme {
ProfilePicture( ProfilePicture(
fallback = "LS",
image = painterResource(id = R.drawable.money_with_wings) image = painterResource(id = R.drawable.money_with_wings)
) )
} }

@ -1,6 +1,7 @@
package fr.iut.alldev.allin.ui.core.bet package fr.iut.alldev.allin.ui.core.bet
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -13,24 +14,22 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
import fr.iut.alldev.allin.R import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.ui.core.ProfilePicture import fr.iut.alldev.allin.ext.asFallbackProfileUsername
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.ProfilePicture
@Composable @Composable
fun BetProfilePictureRow( fun BetProfilePictureRow(
pictures: List<Painter?>, pictures: List<Pair<String, Painter?>>,
maxLength: Int = 5, modifier: Modifier = Modifier,
modifier: Modifier = Modifier maxLength: Int = 5
) { ) {
val nRepeat = remember{ val nRepeat = remember{ pictures.size.coerceAtMost(maxLength) }
if (pictures.size > maxLength) maxLength else pictures.size
}
Box( Box(modifier.width((35 + (nRepeat - 1) * 17).dp)){
modifier.width((nRepeat*17).dp) pictures.take(nRepeat).forEachIndexed { index, (username, painter) ->
){
pictures.forEachIndexed { index, painter ->
ProfilePicture( ProfilePicture(
fallback = username.asFallbackProfileUsername(),
image = painter, image = painter,
size = 35.dp, size = 35.dp,
modifier = Modifier modifier = Modifier
@ -46,24 +45,34 @@ fun BetProfilePictureRow(
@Composable @Composable
private fun BetProfilePictureRowPreview() { private fun BetProfilePictureRowPreview() {
AllInTheme { AllInTheme {
BetProfilePictureRow(pictures = listOf( Box(
painterResource(id = R.drawable.money_with_wings), modifier = Modifier.fillMaxWidth(),
null, contentAlignment = Alignment.Center
painterResource(id = R.drawable.money_with_wings) ) {
)) BetProfilePictureRow(
pictures = listOf("lucas" to null)
)
}
} }
} }
@Preview @Preview
@Composable @Composable
private fun BetProfilePictureRowMaxPreview() { private fun BetProfilePictureRowMaxPreview() {
AllInTheme { AllInTheme {
BetProfilePictureRow(pictures = listOf( Box(
painterResource(id = R.drawable.money_with_wings), modifier = Modifier.fillMaxWidth(),
null, contentAlignment = Alignment.Center
painterResource(id = R.drawable.money_with_wings), ) {
null, BetProfilePictureRow(
painterResource(id = R.drawable.money_with_wings), pictures = listOf(
null "lucas" to painterResource(id = R.drawable.money_with_wings),
)) "lucas" to null,
"lucas" to painterResource(id = R.drawable.money_with_wings),
"lucas" to null,
"lucas" to painterResource(id = R.drawable.money_with_wings),
"lucas" to null
)
)
}
} }
} }

@ -6,6 +6,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import fr.iut.alldev.allin.data.model.User import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.ui.core.AllInLoading import fr.iut.alldev.allin.ui.core.AllInLoading
import fr.iut.alldev.allin.ui.friends.components.FriendsScreenContent import fr.iut.alldev.allin.ui.friends.components.FriendsScreenContent
@ -15,7 +16,7 @@ fun FriendsScreen(
viewModel: FriendsScreenViewModel = hiltViewModel() viewModel: FriendsScreenViewModel = hiltViewModel()
) { ) {
var search by remember { viewModel.search } var search by remember { viewModel.search }
val state by remember { viewModel.state } val state by viewModel.state.collectAsStateWithLifecycle()
when (val s = state) { when (val s = state) {
is FriendsScreenViewModel.State.Loaded -> { is FriendsScreenViewModel.State.Loaded -> {
@ -33,8 +34,10 @@ fun FriendsScreen(
setSearch = { search = it }, setSearch = { search = it },
onToggleDeleteFriend = { onToggleDeleteFriend = {
deleted = if (deleted.contains(it)) { deleted = if (deleted.contains(it)) {
viewModel.addFriend(it.username)
deleted - it deleted - it
} else { } else {
viewModel.removeFriend(it.username)
deleted + it deleted + it
} }
} }

@ -5,150 +5,69 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import fr.iut.alldev.allin.data.model.User import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.data.repository.FriendRepository
import fr.iut.alldev.allin.keystore.AllInKeystoreManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class FriendsScreenViewModel @Inject constructor( class FriendsScreenViewModel @Inject constructor(
private val friendRepository: FriendRepository,
private val keystoreManager: AllInKeystoreManager
) : ViewModel() { ) : ViewModel() {
val search by lazy { mutableStateOf("") } val search by lazy { mutableStateOf("") }
val state by lazy { mutableStateOf<State>(State.Loading) } private val _state by lazy { MutableStateFlow<State>(State.Loading) }
val state get() = _state.asStateFlow()
init { init {
viewModelScope.launch { viewModelScope.launch {
state.value = State.Loaded(mockFriends) try {
_state.emit(
State.Loaded(
friends = friendRepository.getFriends(
token = keystoreManager.getTokenOrEmpty()
)
)
)
} catch (e: Exception) {
Timber.e(e)
}
} }
} }
sealed class State { fun addFriend(username: String) {
data object Loading: State() viewModelScope.launch {
try {
friendRepository.add(
token = keystoreManager.getTokenOrEmpty(),
username = username
)
} catch (e: Exception) {
Timber.e(e)
}
}
}
fun removeFriend(username: String) {
viewModelScope.launch {
try {
friendRepository.remove(
token = keystoreManager.getTokenOrEmpty(),
username = username
)
} catch (e: Exception) {
Timber.e(e)
}
}
}
data class Loaded(val friends: List<User>): State() sealed interface State {
data object Loading : State
data class Loaded(val friends: List<User>) : State
} }
} }
private val mockFriends by lazy {
listOf(
User(
id = "1",
username = "Owen",
email = "",
coins = 8533
),
User(
id = "2",
username = "Dave",
email = "",
coins = 6942
),
User(
id = "3",
username = "Lucas",
email = "",
coins = 3333
),
User(
id = "4",
username = "Louison",
email = "",
coins = 1970
),
User(
id = "5",
username = "Imri",
email = "",
coins = 1
),
User(
id = "12",
username = "Owen",
email = "",
coins = 8533
),
User(
id = "22",
username = "Dave",
email = "",
coins = 6942
),
User(
id = "32",
username = "Lucas",
email = "",
coins = 3333
),
User(
id = "42",
username = "Louison",
email = "",
coins = 1970
),
User(
id = "52",
username = "Imri",
email = "",
coins = 1
),
User(
id = "13",
username = "Owen",
email = "",
coins = 8533
),
User(
id = "23",
username = "Dave",
email = "",
coins = 6942
),
User(
id = "33",
username = "Lucas",
email = "",
coins = 3333
),
User(
id = "43",
username = "Louison",
email = "",
coins = 1970
),
User(
id = "53",
username = "Imri",
email = "",
coins = 1
),
User(
id = "14",
username = "Owen",
email = "",
coins = 8533
),
User(
id = "24",
username = "Dave",
email = "",
coins = 6942
),
User(
id = "34",
username = "Lucas",
email = "",
coins = 3333
),
User(
id = "44",
username = "Louison",
email = "",
coins = 1970
),
User(
id = "54",
username = "Imri",
email = "",
coins = 1
)
)
}

@ -13,6 +13,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.R import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.ext.asFallbackProfileUsername
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.AllInButton import fr.iut.alldev.allin.ui.core.AllInButton
@ -31,6 +32,7 @@ fun FriendsScreenLine(
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
ProfilePicture( ProfilePicture(
fallback = username.asFallbackProfileUsername(),
size = 50.dp size = 50.dp
) )

@ -32,7 +32,7 @@ class MainViewModel @Inject constructor(
var loading = mutableStateOf(false) var loading = mutableStateOf(false)
val currentUser = userRepository.currentUser val currentUser = userRepository.currentUserState
val selectedBet = mutableStateOf<BetDetail?>(null) val selectedBet = mutableStateOf<BetDetail?>(null)
val dismissedEvents = mutableStateListOf<AllInEvent>() val dismissedEvents = mutableStateListOf<AllInEvent>()
val events = mutableStateListOf<AllInEvent>() val events = mutableStateListOf<AllInEvent>()

@ -48,6 +48,10 @@ object Routes {
const val RANKING = "RANKING" const val RANKING = "RANKING"
} }
object Arguments {
const val USER_ID = "USER_ID"
}
private val fadingRoutes private val fadingRoutes
get() = listOf( get() = listOf(
Routes.WELCOME, Routes.WELCOME,

@ -17,6 +17,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.R import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.ext.asFallbackProfileUsername
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.ProfilePicture import fr.iut.alldev.allin.ui.core.ProfilePicture
@ -34,6 +35,7 @@ fun DrawerHeader(
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
ProfilePicture( ProfilePicture(
fallback = username.asFallbackProfileUsername(),
borderWidth = 1.dp borderWidth = 1.dp
) )
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))

@ -6,7 +6,7 @@ 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.NO_VALUE
import fr.iut.alldev.allin.data.model.bet.Participation import fr.iut.alldev.allin.data.model.bet.Participation
import fr.iut.alldev.allin.data.model.bet.YES_VALUE import fr.iut.alldev.allin.data.model.bet.YES_VALUE
import fr.iut.alldev.allin.data.model.bet.YesNoBet import fr.iut.alldev.allin.data.model.bet.BinaryBet
import fr.iut.alldev.allin.data.model.bet.vo.BetAnswerDetail import fr.iut.alldev.allin.data.model.bet.vo.BetAnswerDetail
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
@ -60,7 +60,7 @@ class BetDetailPreviewProvider : PreviewParameterProvider<BetDetail> {
odds = 2.0f odds = 2.0f
) )
) )
is YesNoBet -> listOf( is BinaryBet -> listOf(
BetAnswerDetail( BetAnswerDetail(
response = YES_VALUE, response = YES_VALUE,
totalStakes = 300, totalStakes = 300,

@ -5,12 +5,12 @@ import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.data.model.bet.BetStatus import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.data.model.bet.CustomBet import fr.iut.alldev.allin.data.model.bet.CustomBet
import fr.iut.alldev.allin.data.model.bet.MatchBet 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.BinaryBet
import java.time.ZonedDateTime import java.time.ZonedDateTime
class BetPreviewProvider : PreviewParameterProvider<Bet> { class BetPreviewProvider : PreviewParameterProvider<Bet> {
override val values = sequenceOf( override val values = sequenceOf(
YesNoBet( BinaryBet(
id = "1", id = "1",
theme = "Theme", theme = "Theme",
phrase = "Phrase", phrase = "Phrase",

@ -5,14 +5,14 @@ import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.data.model.bet.BetStatus import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.data.model.bet.CustomBet import fr.iut.alldev.allin.data.model.bet.CustomBet
import fr.iut.alldev.allin.data.model.bet.MatchBet 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.BinaryBet
import java.time.ZonedDateTime import java.time.ZonedDateTime
class BetWithStatusPreviewProvider : PreviewParameterProvider<Bet> { class BetWithStatusPreviewProvider : PreviewParameterProvider<Bet> {
override val values = BetStatus.entries.asSequence().flatMap { status -> override val values = BetStatus.entries.asSequence().flatMap { status ->
sequenceOf( sequenceOf(
YesNoBet( BinaryBet(
id = "1", id = "1",
theme = "Theme", theme = "Theme",
phrase = "Phrase", phrase = "Phrase",

@ -1,9 +0,0 @@
package fr.iut.alldev.allin.ui.profile
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@Composable
fun Profile(){
Text(text = "PROFILE")
}

@ -0,0 +1,32 @@
package fr.iut.alldev.allin.ui.profile
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import fr.iut.alldev.allin.ui.profile.components.ProfileScreenHeader
@Composable
fun ProfileScreen(
viewModel: ProfileViewModel = hiltViewModel()
) {
val state by viewModel.state.collectAsStateWithLifecycle()
Column {
when (val s = state) {
is ProfileViewModel.State.Loaded -> {
ProfileScreenHeader(
username = s.user.username,
totalBets = 333,
bestWin = 365,
friends = 3
)
}
ProfileViewModel.State.Loading -> {
}
}
}
}

@ -0,0 +1,42 @@
package fr.iut.alldev.allin.ui.profile
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.User
import fr.iut.alldev.allin.data.repository.UserRepository
import fr.iut.alldev.allin.ui.navigation.Arguments
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class ProfileViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val userRepository: UserRepository
) : ViewModel() {
private val userId: String? = savedStateHandle[Arguments.USER_ID]
private val _state by lazy { MutableStateFlow<State>(State.Loading) }
val state: StateFlow<State> get() = _state.asStateFlow()
init {
viewModelScope.launch {
userRepository.currentUserState.value?.let { currentUser ->
if (userId == currentUser.id) {
_state.emit(State.Loaded(currentUser))
}
}
}
}
sealed interface State {
data object Loading : State
data class Loaded(val user: User) : State
}
}

@ -0,0 +1,105 @@
package fr.iut.alldev.allin.ui.profile.components
import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.ext.asFallbackProfileUsername
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.ProfilePicture
@Composable
fun ProfileScreenHeader(
username: String,
totalBets: Int,
bestWin: Int,
friends: Int
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
ProfilePicture(
fallback = username.asFallbackProfileUsername(),
size = 68.dp
)
Column(
verticalArrangement = Arrangement.SpaceEvenly,
modifier = Modifier.height(68.dp)
) {
Text(
text = username,
style = AllInTheme.typography.h2,
color = AllInTheme.colors.onMainSurface,
fontSize = 20.sp
)
Row(
horizontalArrangement = Arrangement.spacedBy(20.dp)
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = totalBets.toString(),
style = AllInTheme.typography.h1,
color = AllInTheme.colors.onMainSurface,
fontSize = 14.sp,
)
Text(
text = stringResource(id = R.string.drawer_bets),
style = AllInTheme.typography.p1,
color = AllInTheme.colors.onBackground2
)
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = bestWin.toString(),
style = AllInTheme.typography.h1,
color = AllInTheme.colors.onMainSurface,
fontSize = 14.sp
)
Text(
text = stringResource(id = R.string.drawer_best_win),
style = AllInTheme.typography.p1,
color = AllInTheme.colors.onBackground2
)
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = friends.toString(),
style = AllInTheme.typography.h1,
color = AllInTheme.colors.onMainSurface,
fontSize = 14.sp
)
Text(
text = stringResource(id = R.string.drawer_friends),
style = AllInTheme.typography.p1,
color = AllInTheme.colors.onBackground2
)
}
}
}
}
}
@Preview(showBackground = true)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ProfileScreenHeaderPreview() {
AllInTheme {
ProfileScreenHeader(
username = "User 1",
totalBets = 12,
bestWin = 365,
friends = 5
)
}
}

@ -22,6 +22,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
import fr.iut.alldev.allin.ext.asFallbackProfileUsername
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.AllInCard
@ -87,8 +88,9 @@ fun RankingScreenFirst(
} }
} }
ProfilePicture( ProfilePicture(
fallback = username.asFallbackProfileUsername(),
size = 70.dp,
modifier = Modifier modifier = Modifier
.size(70.dp)
.align(Alignment.TopCenter) .align(Alignment.TopCenter)
.zIndex(1f), .zIndex(1f),
) )

@ -15,6 +15,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.ext.asFallbackProfileUsername
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.AllInCard
@ -48,7 +49,10 @@ fun RankingScreenItem(
modifier = Modifier.size(width = 26.dp, height = 20.dp) modifier = Modifier.size(width = 26.dp, height = 20.dp)
) )
ProfilePicture(modifier = Modifier.size(38.dp)) ProfilePicture(
fallback = username.asFallbackProfileUsername(),
size = 38.dp
)
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))

@ -22,6 +22,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
import fr.iut.alldev.allin.ext.asFallbackProfileUsername
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.AllInCard
@ -87,8 +88,9 @@ fun RankingScreenSecond(
} }
} }
ProfilePicture( ProfilePicture(
fallback = username.asFallbackProfileUsername(),
size = 56.dp,
modifier = Modifier modifier = Modifier
.size(56.dp)
.align(Alignment.TopCenter) .align(Alignment.TopCenter)
.zIndex(1f), .zIndex(1f),
) )

@ -2,9 +2,9 @@ package fr.iut.alldev.allin.vo.bet
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import fr.iut.alldev.allin.data.model.User import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.data.model.bet.BinaryBet
import fr.iut.alldev.allin.data.model.bet.CustomBet import fr.iut.alldev.allin.data.model.bet.CustomBet
import fr.iut.alldev.allin.data.model.bet.MatchBet 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.data.model.bet.vo.BetDetail
interface BetDisplayer { interface BetDisplayer {
@ -13,12 +13,12 @@ interface BetDisplayer {
when (betDetail.bet) { when (betDetail.bet) {
is CustomBet -> DisplayCustomBet(betDetail, currentUser) is CustomBet -> DisplayCustomBet(betDetail, currentUser)
is MatchBet -> DisplayMatchBet(betDetail, currentUser) is MatchBet -> DisplayMatchBet(betDetail, currentUser)
is YesNoBet -> DisplayYesNoBet(betDetail, currentUser) is BinaryBet -> DisplayBinaryBet(betDetail, currentUser)
} }
} }
@Composable @Composable
fun DisplayYesNoBet(betDetail: BetDetail, currentUser: User) fun DisplayBinaryBet(betDetail: BetDetail, currentUser: User)
@Composable @Composable
fun DisplayMatchBet(betDetail: BetDetail, currentUser: User) fun DisplayMatchBet(betDetail: BetDetail, currentUser: User)

@ -3,6 +3,7 @@ package fr.iut.alldev.allin.data.api
import fr.iut.alldev.allin.data.api.model.CheckUser 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.RequestBet
import fr.iut.alldev.allin.data.api.model.RequestBetFilters import fr.iut.alldev.allin.data.api.model.RequestBetFilters
import fr.iut.alldev.allin.data.api.model.RequestFriend
import fr.iut.alldev.allin.data.api.model.RequestParticipation 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.RequestUser
import fr.iut.alldev.allin.data.api.model.ResponseBet import fr.iut.alldev.allin.data.api.model.ResponseBet
@ -26,6 +27,9 @@ interface AllInApi {
this.toRequestBody("text/plain".toMediaTypeOrNull()) this.toRequestBody("text/plain".toMediaTypeOrNull())
} }
// USERS
// ---------------------
@POST("users/login") @POST("users/login")
suspend fun login(@Body body: CheckUser): ResponseUser suspend fun login(@Body body: CheckUser): ResponseUser
@ -35,12 +39,33 @@ interface AllInApi {
@GET("users/token") @GET("users/token")
suspend fun login(@Header("Authorization") token: String): ResponseUser suspend fun login(@Header("Authorization") token: String): ResponseUser
@POST("bets/add")
suspend fun createBet(@Header("Authorization") token: String, @Body body: RequestBet)
@GET("users/gift") @GET("users/gift")
suspend fun dailyGift(@Header("Authorization") token: String): Int suspend fun dailyGift(@Header("Authorization") token: String): Int
// FRIENDS
// ---------------------
@GET("friends/add")
suspend fun getFriends(@Header("Authorization") token: String): List<ResponseUser>
@POST("friends/add")
suspend fun addFriend(
@Header("Authorization") token: String,
@Body request: RequestFriend
)
@POST("friends/delete")
suspend fun deleteFriend(
@Header("Authorization") token: String,
@Body request: RequestFriend
)
// BETS
// ---------------------
@POST("bets/add")
suspend fun createBet(@Header("Authorization") token: String, @Body body: RequestBet)
@POST("bets/gets") @POST("bets/gets")
suspend fun getAllBets( suspend fun getAllBets(
@Header("Authorization") token: String, @Header("Authorization") token: String,
@ -57,12 +82,6 @@ interface AllInApi {
@Body value: RequestBody @Body value: RequestBody
) )
@GET("betdetail/get/{id}")
suspend fun getBet(
@Header("Authorization") token: String,
@Path("id") id: String
): ResponseBetDetail
@GET("bets/current") @GET("bets/current")
suspend fun getBetCurrent( suspend fun getBetCurrent(
@Header("Authorization") token: String @Header("Authorization") token: String
@ -78,6 +97,15 @@ interface AllInApi {
@Header("Authorization") token: String @Header("Authorization") token: String
): List<ResponseBetResultDetail> ): List<ResponseBetResultDetail>
@GET("betdetail/get/{id}")
suspend fun getBet(
@Header("Authorization") token: String,
@Path("id") id: String
): ResponseBetDetail
// PARTICIPATIONS
// ---------------------
@POST("participations/add") @POST("participations/add")
suspend fun participateToBet( suspend fun participateToBet(
@Header("Authorization") token: String, @Header("Authorization") token: String,

@ -3,6 +3,7 @@ package fr.iut.alldev.allin.data.api
import fr.iut.alldev.allin.data.api.model.CheckUser 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.RequestBet
import fr.iut.alldev.allin.data.api.model.RequestBetFilters import fr.iut.alldev.allin.data.api.model.RequestBetFilters
import fr.iut.alldev.allin.data.api.model.RequestFriend
import fr.iut.alldev.allin.data.api.model.RequestParticipation 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.RequestUser
import fr.iut.alldev.allin.data.api.model.ResponseBet import fr.iut.alldev.allin.data.api.model.ResponseBet
@ -28,7 +29,6 @@ import java.util.UUID
class MockAllInApiException(override val message: String?) : Exception() class MockAllInApiException(override val message: String?) : Exception()
class MockAllInApi : AllInApi { class MockAllInApi : AllInApi {
init { init {
CoroutineScope(Dispatchers.Default).launch { CoroutineScope(Dispatchers.Default).launch {
while (true) { while (true) {
@ -135,6 +135,27 @@ class MockAllInApi : AllInApi {
} else throw MockAllInApiException("Gift already taken today") } else throw MockAllInApiException("Gift already taken today")
} }
override suspend fun getFriends(token: String): List<ResponseUser> {
val user = getUserFromToken(token) ?: throw MockAllInApiException("Invalid login/password.")
return mockFriends
.filter { it.first == user.first.id }
.mapNotNull { mockUsers.find { usr -> usr.first.id == it.second }?.first }
}
override suspend fun addFriend(token: String, request: RequestFriend) {
val user = getUserFromToken(token) ?: throw MockAllInApiException("Invalid login/password.")
val requestUser =
mockUsers.find { it.first.username == request.username } ?: throw MockAllInApiException("Requested user not found")
mockFriends.add(user.first.id to requestUser.first.id)
}
override suspend fun deleteFriend(token: String, request: RequestFriend) {
val user = getUserFromToken(token) ?: throw MockAllInApiException("Invalid login/password.")
val requestUser =
mockUsers.find { it.first.username == request.username } ?: throw MockAllInApiException("Requested user not found")
mockFriends.remove(user.first.id to requestUser.first.id)
}
override suspend fun getAllBets(token: String, body: RequestBetFilters): List<ResponseBet> { override suspend fun getAllBets(token: String, body: RequestBetFilters): List<ResponseBet> {
getUserFromToken(token) ?: throw MockAllInApiException("Invalid login/password.") getUserFromToken(token) ?: throw MockAllInApiException("Invalid login/password.")
return mockBets return mockBets
@ -399,6 +420,14 @@ class MockAllInApi : AllInApi {
private val mockWon by lazy { mutableMapOf<String, MutableList<String>>() } private val mockWon by lazy { mutableMapOf<String, MutableList<String>>() }
private val mockGifts by lazy { mutableMapOf<String, ZonedDateTime>() } private val mockGifts by lazy { mutableMapOf<String, ZonedDateTime>() }
private val mockFriends by lazy {
mutableSetOf(
"UUID 1" to "UUID 2",
"UUID 2" to "UUID 1",
"UUID 1" to "UUID 3"
)
}
} }
} }

@ -11,7 +11,7 @@ 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.MatchBet
import fr.iut.alldev.allin.data.model.bet.NO_VALUE 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.YES_VALUE
import fr.iut.alldev.allin.data.model.bet.YesNoBet import fr.iut.alldev.allin.data.model.bet.BinaryBet
import fr.iut.alldev.allin.data.model.bet.vo.BetAnswerDetail import fr.iut.alldev.allin.data.model.bet.vo.BetAnswerDetail
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
import fr.iut.alldev.allin.data.serialization.ZonedDateTimeSerializer import fr.iut.alldev.allin.data.serialization.ZonedDateTimeSerializer
@ -34,7 +34,7 @@ data class ResponseBet(
) { ) {
fun toBet(): Bet = when { fun toBet(): Bet = when {
response.toSet() == setOf(YES_VALUE, NO_VALUE) -> { response.toSet() == setOf(YES_VALUE, NO_VALUE) -> {
YesNoBet( BinaryBet(
id = id ?: "", id = id ?: "",
theme = theme, theme = theme,
phrase = sentenceBet, phrase = sentenceBet,

@ -0,0 +1,8 @@
package fr.iut.alldev.allin.data.api.model
import androidx.annotation.Keep
import kotlinx.serialization.Serializable
@Keep
@Serializable
data class RequestFriend(val username: String)

@ -5,8 +5,10 @@ import dagger.Module
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import fr.iut.alldev.allin.data.repository.BetRepository import fr.iut.alldev.allin.data.repository.BetRepository
import fr.iut.alldev.allin.data.repository.FriendRepository
import fr.iut.alldev.allin.data.repository.UserRepository import fr.iut.alldev.allin.data.repository.UserRepository
import fr.iut.alldev.allin.data.repository.impl.BetRepositoryImpl import fr.iut.alldev.allin.data.repository.impl.BetRepositoryImpl
import fr.iut.alldev.allin.data.repository.impl.FriendRepositoryImpl
import fr.iut.alldev.allin.data.repository.impl.UserRepositoryImpl import fr.iut.alldev.allin.data.repository.impl.UserRepositoryImpl
import javax.inject.Singleton import javax.inject.Singleton
@ -20,4 +22,8 @@ abstract class RepositoryModule {
@Singleton @Singleton
@Binds @Binds
abstract fun provideBetRepository(betRepositoryImpl: BetRepositoryImpl): BetRepository abstract fun provideBetRepository(betRepositoryImpl: BetRepositoryImpl): BetRepository
@Singleton
@Binds
abstract fun provideFriendRepository(friendRepositoryImpl: FriendRepositoryImpl): FriendRepository
} }

@ -20,7 +20,7 @@ class BetFactory {
): Bet = ): Bet =
when (betType) { when (betType) {
BetType.BINARY -> { BetType.BINARY -> {
YesNoBet( BinaryBet(
id = id, id = id,
theme = theme, theme = theme,
creator = creator, creator = creator,

@ -5,7 +5,7 @@ import java.time.ZonedDateTime
const val YES_VALUE = "Yes" const val YES_VALUE = "Yes"
const val NO_VALUE = "No" const val NO_VALUE = "No"
data class YesNoBet( data class BinaryBet(
override val id: String, override val id: String,
override val creator: String, override val creator: String,
override val theme: String, override val theme: String,

@ -0,0 +1,9 @@
package fr.iut.alldev.allin.data.repository
import fr.iut.alldev.allin.data.model.User
abstract class FriendRepository {
abstract suspend fun getFriends(token: String): List<User>
abstract suspend fun add(token: String, username: String)
abstract suspend fun remove(token: String, username: String)
}

@ -6,16 +6,16 @@ import kotlinx.coroutines.flow.asStateFlow
abstract class UserRepository { abstract class UserRepository {
internal val _currentUser by lazy { MutableStateFlow<User?>(null) } internal val currentUser by lazy { MutableStateFlow<User?>(null) }
val currentUser by lazy { _currentUser.asStateFlow() } val currentUserState get() = currentUser.asStateFlow()
suspend fun resetCurrentUser() { suspend fun resetCurrentUser() {
_currentUser.emit(null) currentUser.emit(null)
} }
suspend fun updateCurrentUserCoins(value: Int) { suspend fun updateCurrentUserCoins(value: Int) {
currentUser.value?.let { user -> currentUserState.value?.let { user ->
_currentUser.emit( currentUser.emit(
user.copy( user.copy(
coins = value coins = value
) )
@ -24,9 +24,7 @@ abstract class UserRepository {
} }
abstract suspend fun login(username: String, password: String): String? abstract suspend fun login(username: String, password: String): String?
abstract suspend fun login(token: String): String? abstract suspend fun login(token: String): String?
abstract suspend fun register(username: String, email: String, password: String): String? abstract suspend fun register(username: String, email: String, password: String): String?
abstract suspend fun dailyGift(token: String): Int abstract suspend fun dailyGift(token: String): Int
} }

@ -0,0 +1,30 @@
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.api.model.RequestFriend
import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.data.repository.FriendRepository
import javax.inject.Inject
class FriendRepositoryImpl @Inject constructor(
private val api: AllInApi
) : FriendRepository() {
override suspend fun getFriends(token: String): List<User> {
return api.getFriends(token.formatBearerToken()).map { it.toUser() }
}
override suspend fun add(token: String, username: String) {
return api.addFriend(
token = token.formatBearerToken(),
request = RequestFriend(username)
)
}
override suspend fun remove(token: String, username: String) {
return api.deleteFriend(
token = token.formatBearerToken(),
request = RequestFriend(username)
)
}
}

@ -18,17 +18,16 @@ class UserRepositoryImpl @Inject constructor(
password = password password = password
) )
) )
_currentUser.emit(response.toUser()) currentUser.emit(response.toUser())
return response.token return response.token
} }
override suspend fun login(token: String): String? { override suspend fun login(token: String): String? {
val response = api.login(token = token.formatBearerToken()) val response = api.login(token = token.formatBearerToken())
_currentUser.emit(response.toUser()) currentUser.emit(response.toUser())
return response.token return response.token
} }
override suspend fun register(username: String, email: String, password: String): String? { override suspend fun register(username: String, email: String, password: String): String? {
val response = api.register( val response = api.register(
RequestUser( RequestUser(
@ -37,7 +36,7 @@ class UserRepositoryImpl @Inject constructor(
password = password password = password
) )
) )
_currentUser.emit(response.toUser()) currentUser.emit(response.toUser())
return response.token return response.token
} }

@ -15,7 +15,7 @@ androidxSecurity = "1.1.0-alpha06"
composeBom = "2024.05.00" composeBom = "2024.05.00"
composeCompiler = "1.5.5" composeCompiler = "1.5.5"
composePreview = "1.6.5" composePreview = "1.6.5"
composeNavigation = "2.7.6" composeNavigation = "2.7.7"
hilt = "2.48" hilt = "2.48"
hiltNavigation = "1.1.0" hiltNavigation = "1.1.0"

Loading…
Cancel
Save