Add user stats and pull to refresh in friends screen
continuous-integration/drone/push Build is passing Details

pull/5/head
avalin 11 months ago
parent c4525ad97e
commit 38a42582f3

@ -111,7 +111,15 @@ private fun BetScreenCardPreview() {
time = "13:00", time = "13:00",
totalParticipants = 25, totalParticipants = 25,
players = listOf( players = listOf(
User(id = "", username = "Lucas D", email = "", coins = 0), User(
id = "",
username = "Lucas D",
email = "",
coins = 0,
nbBets = 0,
nbFriends = 0,
bestWin = 0,
)
), ),
onClickParticipate = {}, onClickParticipate = {},
onClickCard = {} onClickCard = {}

@ -2,9 +2,11 @@ package fr.iut.alldev.allin.ui.betCreation.tabs.sections
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
@ -18,11 +20,13 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf 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.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.painterResource
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.unit.dp import androidx.compose.ui.unit.dp
@ -34,6 +38,7 @@ import fr.iut.alldev.allin.ui.betCreation.components.BetCreationScreenFriendLine
import fr.iut.alldev.allin.ui.core.AllInIconChip import fr.iut.alldev.allin.ui.core.AllInIconChip
import fr.iut.alldev.allin.ui.core.AllInRetractableCard import fr.iut.alldev.allin.ui.core.AllInRetractableCard
import fr.iut.alldev.allin.ui.core.AllInTitleInfo import fr.iut.alldev.allin.ui.core.AllInTitleInfo
import fr.iut.alldev.allin.ui.core.bet.AllInEmptyView
@Composable @Composable
fun QuestionTabPrivacySection( fun QuestionTabPrivacySection(
@ -99,9 +104,10 @@ fun QuestionTabPrivacySection(
isOpen = isOpen, isOpen = isOpen,
setIsOpen = { isOpen = it } setIsOpen = { isOpen = it }
) { ) {
Box {
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier
.height(440.dp) .heightIn(max = 440.dp)
.nestedScroll(object : NestedScrollConnection { .nestedScroll(object : NestedScrollConnection {
override fun onPostScroll( override fun onPostScroll(
consumed: Offset, consumed: Offset,
@ -119,8 +125,8 @@ fun QuestionTabPrivacySection(
HorizontalDivider(color = AllInTheme.colors.border) HorizontalDivider(color = AllInTheme.colors.border)
} }
BetCreationScreenFriendLine( BetCreationScreenFriendLine(
username = "Dave", username = it.username,
allCoinsAmount = 542, allCoinsAmount = it.coins,
isSelected = isSelected isSelected = isSelected
) { ) {
if (isSelected) { if (isSelected) {
@ -132,6 +138,19 @@ fun QuestionTabPrivacySection(
} }
} }
} }
if (friends.isEmpty()) {
AllInEmptyView(
text = stringResource(id = R.string.friends_empty_text),
subtext = stringResource(id = R.string.friends_empty_subtext),
image = painterResource(id = R.drawable.silhouettes),
modifier = Modifier
.fillMaxWidth()
.align(Alignment.Center)
)
}
}
} }
Column( Column(
modifier = Modifier.padding(vertical = 20.dp), modifier = Modifier.padding(vertical = 20.dp),

@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListScope
@ -422,7 +421,15 @@ private fun BetStatusBottomSheetPreview(
openParticipateSheet = {} openParticipateSheet = {}
).DisplayBet( ).DisplayBet(
betDetail = bet, betDetail = bet,
currentUser = User(id = "x", username = "aaa", email = "aaa", coins = 150) currentUser = User(
id = "x",
username = "aaa",
email = "aaa",
coins = 150,
nbBets = 0,
nbFriends = 0,
bestWin = 0,
)
) )
} }
} }

@ -40,7 +40,7 @@ fun AllInButton(
enabled = enabled, enabled = enabled,
onClick = onClick, onClick = onClick,
contentPadding = if (isSmall) { contentPadding = if (isSmall) {
PaddingValues(horizontal = 8.dp) PaddingValues(horizontal = 12.dp)
} else ButtonDefaults.ContentPadding } else ButtonDefaults.ContentPadding
) { ) {
Text( Text(

@ -2,6 +2,7 @@ package fr.iut.alldev.allin.ui.friends
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalFocusManager
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.model.FriendStatus import fr.iut.alldev.allin.data.model.FriendStatus
@ -14,6 +15,9 @@ fun FriendsScreen(
val search by viewModel.search.collectAsStateWithLifecycle() val search by viewModel.search.collectAsStateWithLifecycle()
val addTabState by viewModel.addTabState.collectAsStateWithLifecycle() val addTabState by viewModel.addTabState.collectAsStateWithLifecycle()
val requestsTabState by viewModel.requestsTabState.collectAsStateWithLifecycle() val requestsTabState by viewModel.requestsTabState.collectAsStateWithLifecycle()
val refreshing by viewModel.refreshing.collectAsStateWithLifecycle()
val focus = LocalFocusManager.current
FriendsScreenContent( FriendsScreenContent(
addTabState = addTabState, addTabState = addTabState,
@ -28,6 +32,11 @@ fun FriendsScreen(
}, },
requestsTabState = requestsTabState, requestsTabState = requestsTabState,
acceptRequest = { viewModel.addFriend(it.username) }, acceptRequest = { viewModel.addFriend(it.username) },
declineRequest = { viewModel.removeFriend(it.username) } declineRequest = { viewModel.removeFriend(it.username) },
refresh = {
focus.clearFocus()
viewModel.refreshAll()
},
isRefreshing = refreshing
) )
} }

@ -30,6 +30,9 @@ class FriendsScreenViewModel @Inject constructor(
private val _requestsTabState by lazy { MutableStateFlow<RequestsTabState>(RequestsTabState.Loading) } private val _requestsTabState by lazy { MutableStateFlow<RequestsTabState>(RequestsTabState.Loading) }
val requestsTabState get() = _requestsTabState.asStateFlow() val requestsTabState get() = _requestsTabState.asStateFlow()
private val _refreshing by lazy { MutableStateFlow(false) }
val refreshing by lazy { _refreshing.asStateFlow() }
init { init {
viewModelScope.launch { viewModelScope.launch {
try { try {
@ -57,6 +60,27 @@ class FriendsScreenViewModel @Inject constructor(
} }
} }
fun refreshAll() {
viewModelScope.launch {
try {
_refreshing.emit(true)
_addTabState.emit(
_search.value.let { itSearch ->
if (itSearch.isNotBlank()) {
loadSearch(itSearch)
} else {
loadFriends()
}
}
)
_requestsTabState.emit(loadRequests())
} catch (e: Exception) {
Timber.e(e)
}
_refreshing.emit(false)
}
}
private suspend fun loadFriends() = private suspend fun loadFriends() =
AddTabState.Loaded( AddTabState.Loaded(
users = friendRepository.getFriends( users = friendRepository.getFriends(

@ -1,12 +1,25 @@
package fr.iut.alldev.allin.ui.friends.components package fr.iut.alldev.allin.ui.friends.components
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@ -23,20 +36,33 @@ import fr.iut.alldev.allin.ui.core.SectionElement
import fr.iut.alldev.allin.ui.friends.FriendsScreenViewModel import fr.iut.alldev.allin.ui.friends.FriendsScreenViewModel
import fr.iut.alldev.allin.ui.friends.tabs.FriendsScreenAddTab import fr.iut.alldev.allin.ui.friends.tabs.FriendsScreenAddTab
import fr.iut.alldev.allin.ui.friends.tabs.FriendsScreenRequestsTab import fr.iut.alldev.allin.ui.friends.tabs.FriendsScreenRequestsTab
import kotlinx.coroutines.launch
@Composable @Composable
fun FriendsScreenContent( fun FriendsScreenContent(
addTabState: FriendsScreenViewModel.AddTabState, addTabState: FriendsScreenViewModel.AddTabState,
search: String, search: String,
isRefreshing: Boolean,
onToggleDeleteFriend: (User) -> Unit, onToggleDeleteFriend: (User) -> Unit,
setSearch: (String) -> Unit, setSearch: (String) -> Unit,
requestsTabState: FriendsScreenViewModel.RequestsTabState, requestsTabState: FriendsScreenViewModel.RequestsTabState,
acceptRequest: (User) -> Unit, acceptRequest: (User) -> Unit,
declineRequest: (User) -> Unit declineRequest: (User) -> Unit,
refresh: () -> Unit
) { ) {
val focus = LocalFocusManager.current val focus = LocalFocusManager.current
val pullRefreshState = rememberPullRefreshState(isRefreshing, refresh)
val progressAnimation by animateFloatAsState(pullRefreshState.progress * 15, label = "")
val scope = rememberCoroutineScope()
Box(
modifier = Modifier
.pullRefresh(pullRefreshState)
.padding(top = with(LocalDensity.current) {
progressAnimation.toDp()
})
) {
Column(Modifier.fillMaxSize()) { Column(Modifier.fillMaxSize()) {
Text( Text(
@ -51,6 +77,24 @@ fun FriendsScreenContent(
) )
AllInSections( AllInSections(
modifier = Modifier.let {
if (isRefreshing) {
it
.alpha(.5f)
.pointerInput(Unit) {
scope.launch {
awaitPointerEventScope {
while (true) {
awaitPointerEvent(pass = PointerEventPass.Initial)
.changes
.forEach(PointerInputChange::consume)
}
}
}
}
} else it
},
onLoadSection = { focus.clearFocus() }, onLoadSection = { focus.clearFocus() },
sections = listOf( sections = listOf(
SectionElement(stringResource(id = R.string.friends_add_tab)) { SectionElement(stringResource(id = R.string.friends_add_tab)) {
@ -94,6 +138,13 @@ fun FriendsScreenContent(
) )
) )
} }
PullRefreshIndicator(
modifier = Modifier.align(Alignment.TopCenter),
refreshing = isRefreshing,
state = pullRefreshState
)
}
} }
@Preview @Preview
@ -107,7 +158,9 @@ private fun FriendsScreenContentPreview() {
onToggleDeleteFriend = {}, onToggleDeleteFriend = {},
requestsTabState = FriendsScreenViewModel.RequestsTabState.Loaded(emptyList()), requestsTabState = FriendsScreenViewModel.RequestsTabState.Loaded(emptyList()),
acceptRequest = {}, acceptRequest = {},
declineRequest = {} declineRequest = {},
refresh = {},
isRefreshing = false
) )
} }
} }

@ -40,7 +40,7 @@ fun FriendsScreenLine(
Text( Text(
text = username, text = username,
color = AllInTheme.colors.onBackground2, color = AllInTheme.colors.onMainSurface,
style = AllInTheme.typography.sm2, style = AllInTheme.typography.sm2,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,

@ -4,6 +4,10 @@ import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement 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.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
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
@ -39,7 +43,7 @@ fun FriendsScreenRequestLine(
Text( Text(
text = username, text = username,
color = AllInTheme.colors.onBackground2, color = AllInTheme.colors.onMainSurface,
style = AllInTheme.typography.sm2, style = AllInTheme.typography.sm2,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
@ -47,34 +51,28 @@ fun FriendsScreenRequestLine(
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
Row( Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
modifier = Modifier.weight(1.5f),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
AllInButton( AllInButton(
color = AllInColorToken.allInPurple, color = AllInColorToken.allInPurple,
text = stringResource(id = R.string.friends_requests_accept), text = stringResource(id = R.string.friends_requests_accept),
textColor = AllInColorToken.white, textColor = AllInColorToken.white,
isSmall = true, isSmall = true,
textStyle = AllInTheme.typography.sm2, textStyle = AllInTheme.typography.sm2,
onClick = accept, onClick = accept
modifier = Modifier.weight(1f)
) )
AllInButton( IconButton(onClick = decline) {
color = AllInTheme.colors.background, Icon(
text = stringResource(id = R.string.friends_requests_decline), imageVector = Icons.Default.Close,
textColor = AllInTheme.colors.onBackground, contentDescription = stringResource(id = R.string.friends_requests_decline),
isSmall = true, tint = AllInTheme.colors.onBackground2
textStyle = AllInTheme.typography.sm2,
onClick = decline,
modifier = Modifier.weight(1f)
) )
} }
} }
} }
}
@Preview @Preview(showBackground = true)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable @Composable
private fun FriendsScreenLinePreview() { private fun FriendsScreenLinePreview() {

@ -125,9 +125,9 @@ fun MainScreen(
destinations = topLevelDestinations, destinations = topLevelDestinations,
scope = scope, scope = scope,
username = currentUser?.username ?: "", username = currentUser?.username ?: "",
nbFriends = 5, nbFriends = currentUser?.nbFriends ?: 0,
nbBets = 35, nbBets = currentUser?.nbBets ?: 0,
bestWin = 362, bestWin = currentUser?.bestWin ?: 0,
navigateTo = { route -> navigateTo = { route ->
mainViewModel.fetchEvents() mainViewModel.fetchEvents()
navController.popUpTo(route, startDestination) navController.popUpTo(route, startDestination)

@ -105,31 +105,46 @@ private fun RankingScreenContentPreview() {
id = "1", id = "1",
username = "Owen", username = "Owen",
email = "", email = "",
coins = 8533 coins = 8533,
nbBets = 0,
nbFriends = 0,
bestWin = 0
), ),
User( User(
id = "2", id = "2",
username = "Dave", username = "Dave",
email = "", email = "",
coins = 6942 coins = 6942,
nbBets = 0,
nbFriends = 0,
bestWin = 0
), ),
User( User(
id = "3", id = "3",
username = "Lucas", username = "Lucas",
email = "", email = "",
coins = 3333 coins = 3333,
nbBets = 0,
nbFriends = 0,
bestWin = 0
), ),
User( User(
id = "4", id = "4",
username = "Louison", username = "Louison",
email = "", email = "",
coins = 1970 coins = 1970,
nbBets = 0,
nbFriends = 0,
bestWin = 0
), ),
User( User(
id = "5", id = "5",
username = "Imri", username = "Imri",
email = "", email = "",
coins = 1 coins = 1,
nbBets = 0,
nbFriends = 0,
bestWin = 0
) )
) )
) )
@ -146,7 +161,10 @@ private fun RankingScreenContentEmptyPreview() {
id = "1", id = "1",
username = "Owen", username = "Owen",
email = "", email = "",
coins = 8533 coins = 8533,
nbBets = 0,
nbFriends = 0,
bestWin = 0
) )
) )
) )

@ -103,7 +103,10 @@ class MockAllInApi : AllInApi {
username = body.username, username = body.username,
email = body.email, email = body.email,
nbCoins = 500, nbCoins = 500,
token = "${body.username} ${mockUsers.size}" token = "${body.username} ${mockUsers.size}",
nbBets = 0,
nbFriends = 0,
bestWin = 0
) to body.password ) to body.password
mockUsers.add(response) mockUsers.add(response)
return response.first return response.first
@ -369,49 +372,70 @@ class MockAllInApi : AllInApi {
username = "User 1", username = "User 1",
email = "john@doe.fr", email = "john@doe.fr",
nbCoins = 250, nbCoins = 250,
token = "token 1" token = "token 1",
nbBets = 0,
nbFriends = 0,
bestWin = 0
) to "12345", ) to "12345",
ResponseUser( ResponseUser(
id = "UUID 2", id = "UUID 2",
username = "User 2", username = "User 2",
email = "john@doe.fr", email = "john@doe.fr",
nbCoins = 250, nbCoins = 250,
token = "token 2" token = "token 2",
nbBets = 0,
nbFriends = 0,
bestWin = 0
) to "12345", ) to "12345",
ResponseUser( ResponseUser(
id = "UUID 3", id = "UUID 3",
username = "User 3", username = "User 3",
email = "john@doe.fr", email = "john@doe.fr",
nbCoins = 250, nbCoins = 250,
token = "token 3" token = "token 3",
nbBets = 0,
nbFriends = 0,
bestWin = 0
) to "12345", ) to "12345",
ResponseUser( ResponseUser(
id = "UUID 4", id = "UUID 4",
username = "User 4", username = "User 4",
email = "john@doe.fr", email = "john@doe.fr",
nbCoins = 250, nbCoins = 250,
token = "token 4" token = "token 4",
nbBets = 0,
nbFriends = 0,
bestWin = 0
) to "12345", ) to "12345",
ResponseUser( ResponseUser(
id = "UUID 5", id = "UUID 5",
username = "User 5", username = "User 5",
email = "john@doe.fr", email = "john@doe.fr",
nbCoins = 250, nbCoins = 250,
token = "token 5" token = "token 5",
nbBets = 0,
nbFriends = 0,
bestWin = 0
) to "12345", ) to "12345",
ResponseUser( ResponseUser(
id = "UUID 6", id = "UUID 6",
username = "User 6", username = "User 6",
email = "john@doe.fr", email = "john@doe.fr",
nbCoins = 250, nbCoins = 250,
token = "token 6" token = "token 6",
nbBets = 0,
nbFriends = 0,
bestWin = 0
) to "12345", ) to "12345",
ResponseUser( ResponseUser(
id = "UUID 7", id = "UUID 7",
username = "User 7", username = "User 7",
email = "john@doe.fr", email = "john@doe.fr",
nbCoins = 250, nbCoins = 250,
token = "token 7" token = "token 7",
nbBets = 0,
nbFriends = 0,
bestWin = 0
) to "12345" ) to "12345"
) )

@ -21,6 +21,9 @@ data class ResponseUser(
val email: String, val email: String,
var nbCoins: Int, var nbCoins: Int,
var token: String? = null, var token: String? = null,
var nbBets: Int,
var nbFriends: Int,
var bestWin: Int,
val friendStatus: FriendStatus? = null val friendStatus: FriendStatus? = null
) { ) {
fun toUser() = User( fun toUser() = User(
@ -28,7 +31,10 @@ data class ResponseUser(
username = username, username = username,
email = email, email = email,
coins = nbCoins, coins = nbCoins,
friendStatus = friendStatus friendStatus = friendStatus,
nbBets = nbBets,
nbFriends = nbFriends,
bestWin = bestWin
) )
} }

@ -5,5 +5,8 @@ data class User(
val username: String, val username: String,
val email: String, val email: String,
val coins: Int, val coins: Int,
var nbBets: Int,
var nbFriends: Int,
var bestWin: Int,
val friendStatus: FriendStatus? = null val friendStatus: FriendStatus? = null
) )
Loading…
Cancel
Save