Use friend status
continuous-integration/drone/push Build is passing Details

pull/5/head
avalin 6 months ago
parent 7eb97fcb2c
commit 764e84936f

@ -7,7 +7,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
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.FriendStatus
import fr.iut.alldev.allin.ui.core.AllInLoading
import fr.iut.alldev.allin.ui.friends.components.FriendsScreenContent
@ -15,12 +15,13 @@ import fr.iut.alldev.allin.ui.friends.components.FriendsScreenContent
fun FriendsScreen(
viewModel: FriendsScreenViewModel = hiltViewModel()
) {
var search by remember { viewModel.search }
val search by viewModel.search.collectAsStateWithLifecycle()
val state by viewModel.state.collectAsStateWithLifecycle()
when (val s = state) {
is FriendsScreenViewModel.State.Loaded -> {
var deleted by remember { mutableStateOf(emptyList<User>()) }
var deleted by remember { mutableStateOf(emptyList<String>()) }
var requested by remember { mutableStateOf(emptyList<String>()) }
val filteredFriends = remember(search) {
s.friends.filter {
it.username.contains(search, ignoreCase = true)
@ -29,16 +30,21 @@ fun FriendsScreen(
FriendsScreenContent(
friends = filteredFriends,
deleted = deleted,
deletedUsers = deleted,
requestedUsers = requested,
search = search,
setSearch = { search = it },
setSearch = { viewModel.setSearch(it) },
onToggleDeleteFriend = {
deleted = if (deleted.contains(it)) {
deleted = if (deleted.contains(it.id) || it.friendStatus == FriendStatus.NOT_FRIEND) {
viewModel.addFriend(it.username)
deleted - it
requested = requested + it.id
deleted - it.id
} else {
viewModel.removeFriend(it.username)
deleted + it
if (requested.contains(it.id)) {
requested = requested - it.id
}
deleted + it.id
}
}
)

@ -1,6 +1,5 @@
package fr.iut.alldev.allin.ui.friends
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
@ -19,7 +18,9 @@ class FriendsScreenViewModel @Inject constructor(
private val keystoreManager: AllInKeystoreManager
) : ViewModel() {
val search by lazy { mutableStateOf("") }
private val _search by lazy { MutableStateFlow("") }
val search get() = _search.asStateFlow()
private val _state by lazy { MutableStateFlow<State>(State.Loading) }
val state get() = _state.asStateFlow()
@ -36,6 +37,35 @@ class FriendsScreenViewModel @Inject constructor(
} catch (e: Exception) {
Timber.e(e)
}
_search.collect { itSearch ->
try {
_state.emit(
if (itSearch.isNotBlank()) {
State.Loaded(
friends = friendRepository.searchNew(
token = keystoreManager.getTokenOrEmpty(),
search = itSearch
)
)
} else {
State.Loaded(
friends = friendRepository.getFriends(
token = keystoreManager.getTokenOrEmpty()
)
)
}
)
} catch (e: Exception) {
Timber.e(e)
}
}
}
}
fun setSearch(search: String) {
viewModelScope.launch {
_search.emit(search)
}
}

@ -23,6 +23,7 @@ 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.data.model.FriendStatus
import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.ext.asPaddingValues
import fr.iut.alldev.allin.theme.AllInColorToken
@ -32,7 +33,8 @@ import fr.iut.alldev.allin.ui.core.AllInTextField
@Composable
fun FriendsScreenContent(
friends: List<User>,
deleted: List<User>,
deletedUsers: List<String>,
requestedUsers: List<String>,
search: String,
onToggleDeleteFriend: (User) -> Unit,
setSearch: (String) -> Unit,
@ -73,7 +75,11 @@ fun FriendsScreenContent(
items(friends) {
FriendsScreenLine(
username = it.username,
isFriend = it !in deleted,
status = if (it.id in deletedUsers) {
FriendStatus.NOT_FRIEND
} else if (it.id in requestedUsers) {
FriendStatus.REQUESTED
} else it.friendStatus ?: FriendStatus.NOT_FRIEND,
toggleIsFriend = { onToggleDeleteFriend(it) }
)
}
@ -87,7 +93,8 @@ private fun FriendsScreenContentPreview() {
AllInTheme {
FriendsScreenContent(
friends = emptyList(),
deleted = emptyList(),
deletedUsers = emptyList(),
requestedUsers = emptyList(),
search = "",
setSearch = {},
onToggleDeleteFriend = {}

@ -1,5 +1,6 @@
package fr.iut.alldev.allin.ui.friends.components
import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
@ -13,6 +14,7 @@ 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.data.model.FriendStatus
import fr.iut.alldev.allin.ext.asFallbackProfileUsername
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
@ -22,7 +24,7 @@ import fr.iut.alldev.allin.ui.core.ProfilePicture
@Composable
fun FriendsScreenLine(
username: String,
isFriend: Boolean,
status: FriendStatus,
toggleIsFriend: () -> Unit,
modifier: Modifier = Modifier
) {
@ -47,25 +49,31 @@ fun FriendsScreenLine(
)
AllInButton(
color = if (isFriend) {
AllInTheme.colors.background
} else {
AllInColorToken.allInPurple
color = when (status) {
FriendStatus.FRIEND -> AllInTheme.colors.background
FriendStatus.NOT_FRIEND -> AllInColorToken.allInPurple
FriendStatus.REQUESTED -> AllInTheme.colors.border
},
text = if (isFriend) {
stringResource(id = R.string.generic_delete)
} else {
stringResource(id = R.string.generic_add)
text = when (status) {
FriendStatus.FRIEND -> {
stringResource(id = R.string.generic_delete)
}
FriendStatus.NOT_FRIEND -> {
stringResource(id = R.string.generic_add)
}
FriendStatus.REQUESTED -> {
stringResource(id = R.string.friends_request_sent)
}
},
textColor = if (isFriend) {
AllInTheme.colors.onBackground
} else {
AllInColorToken.white
textColor = when (status) {
FriendStatus.FRIEND -> AllInTheme.colors.onBackground
FriendStatus.NOT_FRIEND -> AllInColorToken.white
FriendStatus.REQUESTED -> AllInTheme.colors.onBackground2
},
isSmall = true,
textStyle = AllInTheme.typography.sm2,
onClick = toggleIsFriend,
modifier = Modifier.weight(.5f)
modifier = Modifier.weight(.8f)
)
}
}
@ -76,19 +84,33 @@ private fun FriendsScreenLinePreview() {
AllInTheme {
FriendsScreenLine(
username = "Random",
isFriend = false,
status = FriendStatus.NOT_FRIEND,
toggleIsFriend = { }
)
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun FriendsScreenLineRequestedPreview() {
AllInTheme {
FriendsScreenLine(
username = "Random",
status = FriendStatus.REQUESTED,
toggleIsFriend = { }
)
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun FriendsScreenLineIsFriendPreview() {
AllInTheme {
FriendsScreenLine(
username = "Random",
isFriend = true,
status = FriendStatus.FRIEND,
toggleIsFriend = { }
)
}

@ -3,6 +3,7 @@ package fr.iut.alldev.allin.ui.ranking
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import fr.iut.alldev.allin.data.model.FriendStatus
import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.data.repository.FriendRepository
import fr.iut.alldev.allin.keystore.AllInKeystoreManager
@ -27,7 +28,7 @@ class RankingViewModel @Inject constructor(
State.Loaded(
friends = friendRepository.getFriends(
token = keystoreManager.getTokenOrEmpty()
)
).filter { it.friendStatus == FriendStatus.FRIEND }
)
)
} catch (e: Exception) {

@ -167,7 +167,7 @@
<!--Friends-->
<string name="friends_title">Amis</string>
<string name="friends_request_sent">Requête envoyée</string>
<!--Daily reward-->
<string name="daily_reward_title">Récompense quotidienne</string>
<string name="daily_reward_subtitle">Votre récompense quotidienne est débloquée tous les jours à 00:00 UTC et vous permets dobtenir entre 10 et 150 Allcoins.</string>

@ -164,6 +164,7 @@
<!--Friends-->
<string name="friends_title">Friends</string>
<string name="friends_request_sent">Request sent</string>
<!--Daily reward-->
<string name="daily_reward_title">Daily reward</string>

@ -45,7 +45,7 @@ interface AllInApi {
// FRIENDS
// ---------------------
@GET("friends/add")
@GET("friends/gets")
suspend fun getFriends(@Header("Authorization") token: String): List<ResponseUser>
@POST("friends/add")
@ -60,6 +60,12 @@ interface AllInApi {
@Body request: RequestFriend
)
@GET("friends/search/{search}")
suspend fun searchFriend(
@Header("Authorization") token: String,
@Path("search") search: String
): List<ResponseUser>
// BETS
// ---------------------

@ -13,6 +13,8 @@ import fr.iut.alldev.allin.data.api.model.ResponseBetResult
import fr.iut.alldev.allin.data.api.model.ResponseBetResultDetail
import fr.iut.alldev.allin.data.api.model.ResponseParticipation
import fr.iut.alldev.allin.data.api.model.ResponseUser
import fr.iut.alldev.allin.data.model.FriendStatus
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.BetType
import fr.iut.alldev.allin.data.model.bet.NO_VALUE
@ -71,6 +73,20 @@ class MockAllInApi : AllInApi {
}
}
private fun getFriendStatus(userId: String, withId: String): FriendStatus {
return mockFriends.filter {
it.first == userId && it.second == withId
}.let {
if (it.isEmpty()) FriendStatus.NOT_FRIEND
else mockFriends.filter {
it.second == userId && it.first == withId
}.let {
if (it.isEmpty()) FriendStatus.REQUESTED
else FriendStatus.FRIEND
}
}
}
override suspend fun login(body: CheckUser): ResponseUser {
return mockUsers.find { it.first.username == body.login && it.second == body.password }?.first
?: throw MockAllInApiException("Invalid login/password.")
@ -139,7 +155,13 @@ class MockAllInApi : AllInApi {
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 }
.mapNotNull { itUser ->
mockUsers.find { usr -> usr.first.id == itUser.second }
?.first
?.copy(
friendStatus = getFriendStatus(userId = user.first.id, withId = itUser.second)
)
}
}
override suspend fun addFriend(token: String, request: RequestFriend) {
@ -156,9 +178,51 @@ class MockAllInApi : AllInApi {
mockFriends.remove(user.first.id to requestUser.first.id)
}
override suspend fun searchFriend(token: String, search: String): List<ResponseUser> {
val user = getUserFromToken(token) ?: throw MockAllInApiException("Invalid login/password.")
return mockUsers.filter { it.first.username.contains(search, ignoreCase = true) }
.map { itUser ->
itUser.first.copy(
friendStatus = getFriendStatus(userId = user.first.id, withId = itUser.first.id)
)
}
}
override suspend fun getAllBets(token: String, body: RequestBetFilters): List<ResponseBet> {
getUserFromToken(token) ?: throw MockAllInApiException("Invalid login/password.")
return mockBets
val filters = body.filters
return when {
filters.isEmpty() -> mockBets
filters.size == 1 -> {
val filter = filters[0]
when (filter) {
BetFilter.PUBLIC -> mockBets.filter { !it.isPrivate }
BetFilter.INVITATION -> mockBets.filter { it.isPrivate }
BetFilter.FINISHED -> mockBets.filter { it.status == BetStatus.FINISHED }
BetFilter.IN_PROGRESS -> mockBets.filter {
it.status in listOf(BetStatus.IN_PROGRESS, BetStatus.WAITING, BetStatus.CLOSING)
}
}.map { it }
}
else -> {
mockBets.filter { bet ->
val public = (BetFilter.PUBLIC in filters) && !bet.isPrivate
val invitation = (BetFilter.INVITATION in filters) && bet.isPrivate
val finished =
(BetFilter.FINISHED in filters) and ((bet.status == BetStatus.FINISHED) or (bet.status == BetStatus.CANCELLED))
val inProgress = (BetFilter.IN_PROGRESS in filters) and (bet.status in listOf(
BetStatus.IN_PROGRESS,
BetStatus.WAITING,
BetStatus.CLOSING
))
(public || invitation) && (finished or inProgress)
}.map { it }
}
}
}
override suspend fun getToConfirm(token: String): List<ResponseBetDetail> {

@ -1,6 +1,7 @@
package fr.iut.alldev.allin.data.api.model
import androidx.annotation.Keep
import fr.iut.alldev.allin.data.model.FriendStatus
import fr.iut.alldev.allin.data.model.User
import kotlinx.serialization.Serializable
@ -19,13 +20,15 @@ data class ResponseUser(
val username: String,
val email: String,
var nbCoins: Int,
var token: String? = null
var token: String? = null,
val friendStatus: FriendStatus? = null
) {
fun toUser() = User(
id = id,
username = username,
email = email,
coins = nbCoins
coins = nbCoins,
friendStatus = friendStatus
)
}

@ -0,0 +1,7 @@
package fr.iut.alldev.allin.data.model
enum class FriendStatus {
FRIEND,
REQUESTED,
NOT_FRIEND
}

@ -4,5 +4,6 @@ data class User(
val id: String,
val username: String,
val email: String,
val coins: Int
val coins: Int,
val friendStatus: FriendStatus? = null
)

@ -6,4 +6,5 @@ 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)
abstract suspend fun searchNew(token: String, search: String): List<User>
}

@ -27,4 +27,11 @@ class FriendRepositoryImpl @Inject constructor(
request = RequestFriend(username)
)
}
override suspend fun searchNew(token: String, search: String): List<User> {
return api.searchFriend(
token = token.formatBearerToken(),
search = search
).map { it.toUser() }
}
}
Loading…
Cancel
Save