Add friend requests screen
continuous-integration/drone/push Build is passing Details

pull/5/head
avalin 6 months ago
parent 515f6882c0
commit a2ab066d82

@ -8,8 +8,8 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.data.model.bet.BetType
import fr.iut.alldev.allin.ext.getIcon
import fr.iut.alldev.allin.ext.getTitleId
@ -43,20 +43,8 @@ fun BetCreationScreen(
val registerDateError by remember { viewModel.registerDateError }
val betDateError by remember { viewModel.betDateError }
val friends = remember {
buildList {
repeat(10) {
add(
User(
id = "$it",
username = "Dave",
email = "",
coins = 420
)
)
}
}
}
val friends by viewModel.friends.collectAsStateWithLifecycle()
val selectedFriends = remember { mutableListOf<String>() }
var selectionElements by remember { mutableStateOf(listOf<SelectionElement>()) }
var selectedBetTypeElement by remember { mutableStateOf<SelectionElement?>(null) }

@ -4,12 +4,17 @@ import androidx.compose.runtime.mutableStateOf
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.model.bet.BetFactory
import fr.iut.alldev.allin.data.model.bet.BetType
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.ext.FieldErrorState
import fr.iut.alldev.allin.keystore.AllInKeystoreManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import timber.log.Timber
import java.time.ZonedDateTime
@ -19,7 +24,8 @@ import javax.inject.Inject
class BetCreationViewModel @Inject constructor(
private val betRepository: BetRepository,
private val keystoreManager: AllInKeystoreManager,
private val userRepository: UserRepository
private val userRepository: UserRepository,
private val friendRepository: FriendRepository
) : ViewModel() {
private var hasError = mutableStateOf(false)
@ -35,6 +41,19 @@ class BetCreationViewModel @Inject constructor(
val registerDateError = mutableStateOf<FieldErrorState>(FieldErrorState.NoError)
val betDateError = mutableStateOf<FieldErrorState>(FieldErrorState.NoError)
private val _friends by lazy { MutableStateFlow<List<User>>(emptyList()) }
val friends get() = _friends.asStateFlow()
init {
viewModelScope.launch {
_friends.emit(
friendRepository.getFriends(
token = keystoreManager.getTokenOrEmpty()
).filter { it.friendStatus == FriendStatus.FRIEND }
)
}
}
private fun initErrorField() {
themeError.value = FieldErrorState.NoError
phraseError.value = FieldErrorState.NoError

@ -2,9 +2,11 @@ package fr.iut.alldev.allin.ui.betCreation.tabs
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -34,37 +36,42 @@ fun BetCreationScreenAnswerTab(
mutableStateOf(false)
}
Column(modifier) {
AllInSelectionBox(
isOpen = isOpen,
setIsOpen = { isOpen = it },
selected = selected,
setSelected = setSelected,
elements = elements
)
Spacer(modifier = Modifier.height(26.dp))
when (selectedBetType) {
BetType.BINARY -> {
Column(
modifier = Modifier.padding(vertical = 20.dp),
verticalArrangement = Arrangement.spacedBy(17.dp)
) {
BetCreationScreenBottomText(text = stringResource(id = R.string.bet_creation_yes_no_bottom_text_1))
BetCreationScreenBottomText(text = stringResource(id = R.string.bet_creation_yes_no_bottom_text_2))
LazyColumn(
modifier = modifier,
contentPadding = PaddingValues(start = 20.dp, end = 20.dp, bottom = 120.dp),
verticalArrangement = Arrangement.spacedBy(35.dp)
) {
item {
AllInSelectionBox(
isOpen = isOpen,
setIsOpen = { isOpen = it },
selected = selected,
setSelected = setSelected,
elements = elements
)
Spacer(modifier = Modifier.height(26.dp))
when (selectedBetType) {
BetType.BINARY -> {
Column(
modifier = Modifier.padding(vertical = 20.dp),
verticalArrangement = Arrangement.spacedBy(17.dp)
) {
BetCreationScreenBottomText(text = stringResource(id = R.string.bet_creation_yes_no_bottom_text_1))
BetCreationScreenBottomText(text = stringResource(id = R.string.bet_creation_yes_no_bottom_text_2))
}
}
}
BetType.MATCH -> {
BetCreationScreenBottomText(
text = stringResource(selectedBetType.getTitleId())
)
}
BetType.MATCH -> {
BetCreationScreenBottomText(
text = stringResource(selectedBetType.getTitleId())
)
}
BetType.CUSTOM -> {
BetCreationScreenBottomText(
text = stringResource(selectedBetType.getTitleId())
)
BetType.CUSTOM -> {
BetCreationScreenBottomText(
text = stringResource(selectedBetType.getTitleId())
)
}
}
}
}

@ -1,9 +1,9 @@
package fr.iut.alldev.allin.ui.betCreation.tabs
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
@ -41,39 +41,46 @@ fun BetCreationScreenQuestionTab(
setEndTimeDialog: (Boolean) -> Unit,
interactionSource: MutableInteractionSource
) {
Column(modifier = modifier) {
QuestionTabThemePhraseSection(
betTheme = betTheme,
betThemeError = betThemeError,
setBetTheme = setBetTheme,
betPhrase = betPhrase,
betPhraseError = betPhraseError,
setBetPhrase = setBetPhrase,
interactionSource = interactionSource
)
Spacer(modifier = Modifier.height(35.dp))
QuestionTabDateTimeSection(
registerDate = registerDate.formatToMediumDate(),
registerTime = registerDate.formatToTime1(),
registerDateError = registerDateError,
betDateError = betDateError,
endDate = betDate.formatToMediumDate(),
endTime = betDate.formatToTime1(),
setEndDateDialog = setEndDateDialog,
setRegisterDateDialog = setRegisterDateDialog,
setRegisterTimeDialog = setRegisterTimeDialog,
setEndTimeDialog = setEndTimeDialog,
interactionSource = interactionSource,
)
Spacer(modifier = Modifier.height(35.dp))
QuestionTabPrivacySection(
isPublic = isPublic,
setIsPublic = setIsPublic,
friends = friends,
selectedFriends = selectedFriends,
interactionSource = interactionSource
)
Spacer(modifier = Modifier.height(120.dp))
LazyColumn(
modifier = modifier,
contentPadding = PaddingValues(start = 20.dp, end = 20.dp, bottom = 120.dp),
verticalArrangement = Arrangement.spacedBy(35.dp)
) {
item {
QuestionTabThemePhraseSection(
betTheme = betTheme,
betThemeError = betThemeError,
setBetTheme = setBetTheme,
betPhrase = betPhrase,
betPhraseError = betPhraseError,
setBetPhrase = setBetPhrase,
interactionSource = interactionSource
)
}
item {
QuestionTabDateTimeSection(
registerDate = registerDate.formatToMediumDate(),
registerTime = registerDate.formatToTime1(),
registerDateError = registerDateError,
betDateError = betDateError,
endDate = betDate.formatToMediumDate(),
endTime = betDate.formatToTime1(),
setEndDateDialog = setEndDateDialog,
setRegisterDateDialog = setRegisterDateDialog,
setRegisterTimeDialog = setRegisterTimeDialog,
setEndTimeDialog = setEndTimeDialog,
interactionSource = interactionSource,
)
}
item {
QuestionTabPrivacySection(
isPublic = isPublic,
setIsPublic = setIsPublic,
friends = friends,
selectedFriends = selectedFriends,
interactionSource = interactionSource
)
}
}
}

@ -1,5 +1,6 @@
package fr.iut.alldev.allin.ui.core
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
@ -37,14 +38,17 @@ fun AllInButton(
),
modifier = modifier,
enabled = enabled,
onClick = onClick
onClick = onClick,
contentPadding = if (isSmall) {
PaddingValues(horizontal = 8.dp)
} else ButtonDefaults.ContentPadding
) {
Text(
text = text,
textAlign = TextAlign.Center,
style = textStyle,
color = if (enabled) textColor else AllInTheme.colors.disabledBorder,
fontSize = if (isSmall) 15.sp else 20.sp,
fontSize = if (isSmall) 12.sp else 20.sp,
modifier = Modifier.padding(vertical = if (isSmall) 0.dp else 8.dp)
)
}

@ -5,11 +5,9 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.pager.HorizontalPager
@ -47,13 +45,12 @@ fun AllInSections(
Box(modifier = modifier) {
HorizontalPager(state = pagerState) { page ->
LaunchedEffect(key1 = page) { onLoadSection() }
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(top = 40.dp, start = 20.dp, end = 20.dp)
Box(
modifier = Modifier
.fillMaxSize()
.padding(top = 40.dp)
) {
item {
sections[page].content()
}
sections[page].content()
}
}

@ -2,13 +2,9 @@ package fr.iut.alldev.allin.ui.friends
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
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.FriendStatus
import fr.iut.alldev.allin.ui.core.AllInLoading
import fr.iut.alldev.allin.ui.friends.components.FriendsScreenContent
@Composable
@ -16,37 +12,22 @@ fun FriendsScreen(
viewModel: FriendsScreenViewModel = hiltViewModel()
) {
val search by viewModel.search.collectAsStateWithLifecycle()
val state by viewModel.state.collectAsStateWithLifecycle()
val addTabState by viewModel.addTabState.collectAsStateWithLifecycle()
val requestsTabState by viewModel.requestsTabState.collectAsStateWithLifecycle()
when (val s = state) {
is FriendsScreenViewModel.State.Loaded -> {
var deleted by remember { mutableStateOf(emptyList<String>()) }
var requested by remember { mutableStateOf(emptyList<String>()) }
FriendsScreenContent(
friends = s.friends,
deletedUsers = deleted,
requestedUsers = requested,
search = search,
setSearch = { viewModel.setSearch(it) },
onToggleDeleteFriend = {
deleted = if (deleted.contains(it.id) || it.friendStatus == FriendStatus.NOT_FRIEND) {
viewModel.addFriend(it.username)
requested = requested + it.id
deleted - it.id
} else {
viewModel.removeFriend(it.username)
if (requested.contains(it.id)) {
requested = requested - it.id
}
deleted + it.id
}
}
)
}
FriendsScreenViewModel.State.Loading -> {
AllInLoading(visible = true)
}
}
FriendsScreenContent(
addTabState = addTabState,
search = search,
setSearch = { viewModel.setSearch(it) },
onToggleDeleteFriend = {
if (it.friendStatus == FriendStatus.NOT_FRIEND) {
viewModel.addFriend(it.username)
} else {
viewModel.removeFriend(it.username)
}
},
requestsTabState = requestsTabState,
acceptRequest = { viewModel.addFriend(it.username) },
declineRequest = { viewModel.removeFriend(it.username) }
)
}

@ -3,6 +3,7 @@ package fr.iut.alldev.allin.ui.friends
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
@ -23,19 +24,17 @@ class FriendsScreenViewModel @Inject constructor(
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()
private val _addTabState by lazy { MutableStateFlow<AddTabState>(AddTabState.Loading) }
val addTabState get() = _addTabState.asStateFlow()
private val _requestsTabState by lazy { MutableStateFlow<RequestsTabState>(RequestsTabState.Loading) }
val requestsTabState get() = _requestsTabState.asStateFlow()
init {
viewModelScope.launch {
try {
_state.emit(
State.Loaded(
friends = friendRepository.getFriends(
token = keystoreManager.getTokenOrEmpty()
)
)
)
_addTabState.emit(loadFriends())
_requestsTabState.emit(loadRequests())
} catch (e: Exception) {
Timber.e(e)
}
@ -44,20 +43,11 @@ class FriendsScreenViewModel @Inject constructor(
.debounce(1.seconds)
.collect { itSearch ->
try {
_state.emit(
_addTabState.emit(
if (itSearch.isNotBlank()) {
State.Loaded(
friends = friendRepository.searchNew(
token = keystoreManager.getTokenOrEmpty(),
search = itSearch
)
)
loadSearch(itSearch)
} else {
State.Loaded(
friends = friendRepository.getFriends(
token = keystoreManager.getTokenOrEmpty()
)
)
loadFriends()
}
)
} catch (e: Exception) {
@ -67,6 +57,28 @@ class FriendsScreenViewModel @Inject constructor(
}
}
private suspend fun loadFriends() =
AddTabState.Loaded(
users = friendRepository.getFriends(
token = keystoreManager.getTokenOrEmpty()
)
)
private suspend fun loadSearch(search: String) =
AddTabState.Loaded(
users = friendRepository.searchNew(
token = keystoreManager.getTokenOrEmpty(),
search = search
)
)
private suspend fun loadRequests() =
RequestsTabState.Loaded(
users = friendRepository.getFriendRequests(
token = keystoreManager.getTokenOrEmpty()
)
)
fun setSearch(search: String) {
viewModelScope.launch {
_search.emit(search)
@ -80,6 +92,8 @@ class FriendsScreenViewModel @Inject constructor(
token = keystoreManager.getTokenOrEmpty(),
username = username
)
changeFriendStatus(username, FriendStatus.REQUESTED)
removeRequest(username)
} catch (e: Exception) {
Timber.e(e)
}
@ -93,15 +107,53 @@ class FriendsScreenViewModel @Inject constructor(
token = keystoreManager.getTokenOrEmpty(),
username = username
)
changeFriendStatus(username, FriendStatus.NOT_FRIEND)
removeRequest(username)
} catch (e: Exception) {
Timber.e(e)
}
}
}
sealed interface State {
data object Loading : State
data class Loaded(val friends: List<User>) : State
private suspend fun changeFriendStatus(username: String, newStatus: FriendStatus) {
(_addTabState.value as? AddTabState.Loaded)?.let { friends ->
val newList = friends.users.toMutableList()
newList.replaceAll {
if (it.username == username) {
it.copy(friendStatus = newStatus)
} else it
}
_addTabState.emit(
friends.copy(
users = newList
)
)
}
}
private suspend fun removeRequest(username: String) {
(_requestsTabState.value as? RequestsTabState.Loaded)?.let { requests ->
requests.users.find { it.username == username }?.let {
_requestsTabState.emit(
requests.copy(
users = requests.users - it
)
)
}
}
}
sealed interface AddTabState {
data object Loading : AddTabState
data class Loaded(val users: List<User>) : AddTabState
}
sealed interface RequestsTabState {
data object Loading : RequestsTabState
data class Loaded(val users: List<User>) : RequestsTabState
}
}

@ -1,89 +1,92 @@
package fr.iut.alldev.allin.ui.friends.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
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
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInTextField
import fr.iut.alldev.allin.ui.core.AllInLoading
import fr.iut.alldev.allin.ui.core.AllInSections
import fr.iut.alldev.allin.ui.core.SectionElement
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.FriendsScreenRequestsTab
@Composable
fun FriendsScreenContent(
friends: List<User>,
deletedUsers: List<String>,
requestedUsers: List<String>,
addTabState: FriendsScreenViewModel.AddTabState,
search: String,
onToggleDeleteFriend: (User) -> Unit,
setSearch: (String) -> Unit,
requestsTabState: FriendsScreenViewModel.RequestsTabState,
acceptRequest: (User) -> Unit,
declineRequest: (User) -> Unit
) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = WindowInsets.navigationBars.asPaddingValues(start = 24.dp, end = 24.dp, top = 18.dp),
verticalArrangement = Arrangement.spacedBy(11.dp),
) {
item {
Text(
text = stringResource(id = R.string.friends_title),
style = AllInTheme.typography.h1,
color = AllInColorToken.allInGrey,
fontSize = 24.sp,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
val focus = LocalFocusManager.current
stickyHeader {
AllInTextField(
value = search,
onValueChange = setSearch,
leadingIcon = rememberVectorPainter(image = Icons.Default.Search),
modifier = Modifier
.background(
Brush.verticalGradient(
0.5f to AllInTheme.colors.mainSurface,
1f to Color.Transparent
)
)
.fillMaxWidth()
.padding(vertical = 8.dp)
)
}
Column(Modifier.fillMaxSize()) {
items(friends) {
FriendsScreenLine(
username = it.username,
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) }
)
}
Text(
text = stringResource(id = R.string.friends_title),
style = AllInTheme.typography.h1,
color = AllInColorToken.allInGrey,
fontSize = 24.sp,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 11.dp)
)
AllInSections(
onLoadSection = { focus.clearFocus() },
sections = listOf(
SectionElement(stringResource(id = R.string.friends_add_tab)) {
when (addTabState) {
is FriendsScreenViewModel.AddTabState.Loaded -> {
FriendsScreenAddTab(
friends = addTabState.users,
search = search,
setSearch = setSearch,
onToggleDeleteFriend = onToggleDeleteFriend
)
}
FriendsScreenViewModel.AddTabState.Loading -> {
AllInLoading(visible = true)
}
}
},
SectionElement(stringResource(id = R.string.friends_requests_tab)) {
when (requestsTabState) {
is FriendsScreenViewModel.RequestsTabState.Loaded -> {
FriendsScreenRequestsTab(
requests = requestsTabState.users,
acceptRequest = acceptRequest,
declineRequest = declineRequest
)
}
FriendsScreenViewModel.RequestsTabState.Loading -> {
AllInLoading(visible = true)
}
}
}
)
)
}
}
@ -92,12 +95,13 @@ fun FriendsScreenContent(
private fun FriendsScreenContentPreview() {
AllInTheme {
FriendsScreenContent(
friends = emptyList(),
deletedUsers = emptyList(),
requestedUsers = emptyList(),
addTabState = FriendsScreenViewModel.AddTabState.Loaded(emptyList()),
search = "",
setSearch = {},
onToggleDeleteFriend = {}
onToggleDeleteFriend = {},
requestsTabState = FriendsScreenViewModel.RequestsTabState.Loaded(emptyList()),
acceptRequest = {},
declineRequest = {}
)
}
}

@ -0,0 +1,88 @@
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
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.text.style.TextOverflow
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.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInButton
import fr.iut.alldev.allin.ui.core.ProfilePicture
@Composable
fun FriendsScreenRequestLine(
username: String,
accept: () -> Unit,
decline: () -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
ProfilePicture(
fallback = username.asFallbackProfileUsername(),
size = 50.dp
)
Text(
text = username,
color = AllInTheme.colors.onBackground2,
style = AllInTheme.typography.sm2,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
fontSize = 15.sp,
modifier = Modifier.weight(1f)
)
Row(
modifier = Modifier.weight(1.5f),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
AllInButton(
color = AllInColorToken.allInPurple,
text = stringResource(id = R.string.friends_requests_accept),
textColor = AllInColorToken.white,
isSmall = true,
textStyle = AllInTheme.typography.sm2,
onClick = accept,
modifier = Modifier.weight(1f)
)
AllInButton(
color = AllInTheme.colors.background,
text = stringResource(id = R.string.friends_requests_decline),
textColor = AllInTheme.colors.onBackground,
isSmall = true,
textStyle = AllInTheme.typography.sm2,
onClick = decline,
modifier = Modifier.weight(1f)
)
}
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun FriendsScreenLinePreview() {
AllInTheme {
FriendsScreenRequestLine(
username = "Xx_Bg_du_63",
accept = { },
decline = { }
)
}
}

@ -0,0 +1,65 @@
package fr.iut.alldev.allin.ui.friends.tabs
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.unit.dp
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.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInTextField
import fr.iut.alldev.allin.ui.friends.components.FriendsScreenLine
@Composable
fun FriendsScreenAddTab(
friends: List<User>,
search: String,
onToggleDeleteFriend: (User) -> Unit,
setSearch: (String) -> Unit,
) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = WindowInsets.navigationBars.asPaddingValues(start = 24.dp, end = 24.dp, top = 18.dp),
verticalArrangement = Arrangement.spacedBy(11.dp),
) {
stickyHeader {
AllInTextField(
value = search,
onValueChange = setSearch,
leadingIcon = rememberVectorPainter(image = Icons.Default.Search),
modifier = Modifier
.background(
Brush.verticalGradient(
0.5f to AllInTheme.colors.mainSurface,
1f to Color.Transparent
)
)
.fillMaxWidth()
.padding(vertical = 8.dp)
)
}
items(friends) {
FriendsScreenLine(
username = it.username,
status = it.friendStatus ?: FriendStatus.NOT_FRIEND,
toggleIsFriend = { onToggleDeleteFriend(it) }
)
}
}
}

@ -0,0 +1,36 @@
package fr.iut.alldev.allin.ui.friends.tabs
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.ext.asPaddingValues
import fr.iut.alldev.allin.ui.friends.components.FriendsScreenRequestLine
@Composable
fun FriendsScreenRequestsTab(
requests: List<User>,
acceptRequest: (User) -> Unit,
declineRequest: (User) -> Unit
) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = WindowInsets.navigationBars.asPaddingValues(start = 24.dp, end = 24.dp, top = 18.dp),
verticalArrangement = Arrangement.spacedBy(11.dp),
) {
items(requests) {
FriendsScreenRequestLine(
username = it.username,
accept = { acceptRequest(it) },
decline = { declineRequest(it) }
)
}
}
}

@ -181,6 +181,7 @@ fun MainScreen(
when (val event = it) {
is ToConfirmBet -> {
Timber.d("ToConfirmBet")
scope.launch { eventBottomSheetState.show() }
event.Display(sheetState = eventBottomSheetState) {
mainViewModel.dismissedEvents += it
scope.launch {
@ -193,6 +194,7 @@ fun MainScreen(
is WonBet -> {
Timber.d("WonBet")
scope.launch { eventBottomSheetState.show() }
event.Display(sheetState = eventBottomSheetState) {
mainViewModel.dismissedEvents += it
scope.launch {

@ -168,6 +168,11 @@
<!--Friends-->
<string name="friends_title">Amis</string>
<string name="friends_request_sent">Requête envoyée</string>
<string name="friends_add_tab">Ajouter</string>
<string name="friends_requests_tab">Demandes</string>
<string name="friends_requests_accept">Accepter</string>
<string name="friends_requests_decline">Refuser</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>

@ -165,6 +165,10 @@
<!--Friends-->
<string name="friends_title">Friends</string>
<string name="friends_request_sent">Request sent</string>
<string name="friends_add_tab">Add friends</string>
<string name="friends_requests_tab">Requests</string>
<string name="friends_requests_accept">Accept</string>
<string name="friends_requests_decline">Decline</string>
<!--Daily reward-->
<string name="daily_reward_title">Daily reward</string>

@ -48,6 +48,9 @@ interface AllInApi {
@GET("friends/gets")
suspend fun getFriends(@Header("Authorization") token: String): List<ResponseUser>
@GET("friends/requests")
suspend fun getFriendRequests(@Header("Authorization") token: String): List<ResponseUser>
@POST("friends/add")
suspend fun addFriend(
@Header("Authorization") token: String,

@ -164,6 +164,22 @@ class MockAllInApi : AllInApi {
}
}
override suspend fun getFriendRequests(token: String): List<ResponseUser> {
val user = getUserFromToken(token) ?: throw MockAllInApiException("Invalid login/password.")
return mockFriends
.filter {
(it.second == user.first.id) &&
mockFriends.none { it2 ->
it2.first == user.first.id && it2.second == it.first
}
}
.mapNotNull { itUser ->
mockUsers.find { usr -> usr.first.id == itUser.first }
?.first
?.copy(friendStatus = FriendStatus.NOT_FRIEND)
}
}
override suspend fun addFriend(token: String, request: RequestFriend) {
val user = getUserFromToken(token) ?: throw MockAllInApiException("Invalid login/password.")
val requestUser =

@ -4,6 +4,7 @@ import fr.iut.alldev.allin.data.model.User
abstract class FriendRepository {
abstract suspend fun getFriends(token: String): List<User>
abstract suspend fun getFriendRequests(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>

@ -14,6 +14,10 @@ class FriendRepositoryImpl @Inject constructor(
return api.getFriends(token.formatBearerToken()).map { it.toUser() }
}
override suspend fun getFriendRequests(token: String): List<User> {
return api.getFriendRequests(token.formatBearerToken()).map { it.toUser() }
}
override suspend fun add(token: String, username: String) {
return api.addFriend(
token = token.formatBearerToken(),

Loading…
Cancel
Save