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

pull/5/head
avalin 1 year ago
parent 515f6882c0
commit a2ab066d82

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

@ -4,12 +4,17 @@ import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel 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.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.BetFactory
import fr.iut.alldev.allin.data.model.bet.BetType import fr.iut.alldev.allin.data.model.bet.BetType
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.ext.FieldErrorState import fr.iut.alldev.allin.ext.FieldErrorState
import fr.iut.alldev.allin.keystore.AllInKeystoreManager 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 timber.log.Timber
import java.time.ZonedDateTime import java.time.ZonedDateTime
@ -19,7 +24,8 @@ import javax.inject.Inject
class BetCreationViewModel @Inject constructor( class BetCreationViewModel @Inject constructor(
private val betRepository: BetRepository, private val betRepository: BetRepository,
private val keystoreManager: AllInKeystoreManager, private val keystoreManager: AllInKeystoreManager,
private val userRepository: UserRepository private val userRepository: UserRepository,
private val friendRepository: FriendRepository
) : ViewModel() { ) : ViewModel() {
private var hasError = mutableStateOf(false) private var hasError = mutableStateOf(false)
@ -35,6 +41,19 @@ class BetCreationViewModel @Inject constructor(
val registerDateError = mutableStateOf<FieldErrorState>(FieldErrorState.NoError) val registerDateError = mutableStateOf<FieldErrorState>(FieldErrorState.NoError)
val betDateError = 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() { private fun initErrorField() {
themeError.value = FieldErrorState.NoError themeError.value = FieldErrorState.NoError
phraseError.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.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
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.lazy.LazyColumn
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -34,37 +36,42 @@ fun BetCreationScreenAnswerTab(
mutableStateOf(false) mutableStateOf(false)
} }
LazyColumn(
Column(modifier) { modifier = modifier,
AllInSelectionBox( contentPadding = PaddingValues(start = 20.dp, end = 20.dp, bottom = 120.dp),
isOpen = isOpen, verticalArrangement = Arrangement.spacedBy(35.dp)
setIsOpen = { isOpen = it }, ) {
selected = selected, item {
setSelected = setSelected, AllInSelectionBox(
elements = elements isOpen = isOpen,
) setIsOpen = { isOpen = it },
Spacer(modifier = Modifier.height(26.dp)) selected = selected,
when (selectedBetType) { setSelected = setSelected,
BetType.BINARY -> { elements = elements
Column( )
modifier = Modifier.padding(vertical = 20.dp), Spacer(modifier = Modifier.height(26.dp))
verticalArrangement = Arrangement.spacedBy(17.dp) when (selectedBetType) {
) { BetType.BINARY -> {
BetCreationScreenBottomText(text = stringResource(id = R.string.bet_creation_yes_no_bottom_text_1)) Column(
BetCreationScreenBottomText(text = stringResource(id = R.string.bet_creation_yes_no_bottom_text_2)) 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 -> { BetType.MATCH -> {
BetCreationScreenBottomText( BetCreationScreenBottomText(
text = stringResource(selectedBetType.getTitleId()) text = stringResource(selectedBetType.getTitleId())
) )
} }
BetType.CUSTOM -> { BetType.CUSTOM -> {
BetCreationScreenBottomText( BetCreationScreenBottomText(
text = stringResource(selectedBetType.getTitleId()) text = stringResource(selectedBetType.getTitleId())
) )
}
} }
} }
} }

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

@ -1,5 +1,6 @@
package fr.iut.alldev.allin.ui.core package fr.iut.alldev.allin.ui.core
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
@ -37,14 +38,17 @@ fun AllInButton(
), ),
modifier = modifier, modifier = modifier,
enabled = enabled, enabled = enabled,
onClick = onClick onClick = onClick,
contentPadding = if (isSmall) {
PaddingValues(horizontal = 8.dp)
} else ButtonDefaults.ContentPadding
) { ) {
Text( Text(
text = text, text = text,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
style = textStyle, style = textStyle,
color = if (enabled) textColor else AllInTheme.colors.disabledBorder, 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) 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.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
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.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.HorizontalPager
@ -47,13 +45,12 @@ fun AllInSections(
Box(modifier = modifier) { Box(modifier = modifier) {
HorizontalPager(state = pagerState) { page -> HorizontalPager(state = pagerState) { page ->
LaunchedEffect(key1 = page) { onLoadSection() } LaunchedEffect(key1 = page) { onLoadSection() }
LazyColumn( Box(
modifier = Modifier.fillMaxSize(), modifier = Modifier
contentPadding = PaddingValues(top = 40.dp, start = 20.dp, end = 20.dp) .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.Composable
import androidx.compose.runtime.getValue 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.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
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
@Composable @Composable
@ -16,37 +12,22 @@ fun FriendsScreen(
viewModel: FriendsScreenViewModel = hiltViewModel() viewModel: FriendsScreenViewModel = hiltViewModel()
) { ) {
val search by viewModel.search.collectAsStateWithLifecycle() 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) { FriendsScreenContent(
is FriendsScreenViewModel.State.Loaded -> { addTabState = addTabState,
var deleted by remember { mutableStateOf(emptyList<String>()) } search = search,
var requested by remember { mutableStateOf(emptyList<String>()) } setSearch = { viewModel.setSearch(it) },
onToggleDeleteFriend = {
FriendsScreenContent( if (it.friendStatus == FriendStatus.NOT_FRIEND) {
friends = s.friends, viewModel.addFriend(it.username)
deletedUsers = deleted, } else {
requestedUsers = requested, viewModel.removeFriend(it.username)
search = search, }
setSearch = { viewModel.setSearch(it) }, },
onToggleDeleteFriend = { requestsTabState = requestsTabState,
deleted = if (deleted.contains(it.id) || it.friendStatus == FriendStatus.NOT_FRIEND) { acceptRequest = { viewModel.addFriend(it.username) },
viewModel.addFriend(it.username) declineRequest = { viewModel.removeFriend(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)
}
}
} }

@ -3,6 +3,7 @@ package fr.iut.alldev.allin.ui.friends
import androidx.lifecycle.ViewModel 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.FriendStatus
import fr.iut.alldev.allin.data.model.User import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.data.repository.FriendRepository import fr.iut.alldev.allin.data.repository.FriendRepository
import fr.iut.alldev.allin.keystore.AllInKeystoreManager import fr.iut.alldev.allin.keystore.AllInKeystoreManager
@ -23,19 +24,17 @@ class FriendsScreenViewModel @Inject constructor(
private val _search by lazy { MutableStateFlow("") } private val _search by lazy { MutableStateFlow("") }
val search get() = _search.asStateFlow() val search get() = _search.asStateFlow()
private val _state by lazy { MutableStateFlow<State>(State.Loading) } private val _addTabState by lazy { MutableStateFlow<AddTabState>(AddTabState.Loading) }
val state get() = _state.asStateFlow() val addTabState get() = _addTabState.asStateFlow()
private val _requestsTabState by lazy { MutableStateFlow<RequestsTabState>(RequestsTabState.Loading) }
val requestsTabState get() = _requestsTabState.asStateFlow()
init { init {
viewModelScope.launch { viewModelScope.launch {
try { try {
_state.emit( _addTabState.emit(loadFriends())
State.Loaded( _requestsTabState.emit(loadRequests())
friends = friendRepository.getFriends(
token = keystoreManager.getTokenOrEmpty()
)
)
)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e(e)
} }
@ -44,20 +43,11 @@ class FriendsScreenViewModel @Inject constructor(
.debounce(1.seconds) .debounce(1.seconds)
.collect { itSearch -> .collect { itSearch ->
try { try {
_state.emit( _addTabState.emit(
if (itSearch.isNotBlank()) { if (itSearch.isNotBlank()) {
State.Loaded( loadSearch(itSearch)
friends = friendRepository.searchNew(
token = keystoreManager.getTokenOrEmpty(),
search = itSearch
)
)
} else { } else {
State.Loaded( loadFriends()
friends = friendRepository.getFriends(
token = keystoreManager.getTokenOrEmpty()
)
)
} }
) )
} catch (e: Exception) { } 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) { fun setSearch(search: String) {
viewModelScope.launch { viewModelScope.launch {
_search.emit(search) _search.emit(search)
@ -80,6 +92,8 @@ class FriendsScreenViewModel @Inject constructor(
token = keystoreManager.getTokenOrEmpty(), token = keystoreManager.getTokenOrEmpty(),
username = username username = username
) )
changeFriendStatus(username, FriendStatus.REQUESTED)
removeRequest(username)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e(e)
} }
@ -93,15 +107,53 @@ class FriendsScreenViewModel @Inject constructor(
token = keystoreManager.getTokenOrEmpty(), token = keystoreManager.getTokenOrEmpty(),
username = username username = username
) )
changeFriendStatus(username, FriendStatus.NOT_FRIEND)
removeRequest(username)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e(e)
} }
} }
} }
sealed interface State { private suspend fun changeFriendStatus(username: String, newStatus: FriendStatus) {
data object Loading : State (_addTabState.value as? AddTabState.Loaded)?.let { friends ->
data class Loaded(val friends: List<User>) : State
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 package fr.iut.alldev.allin.ui.friends.components
import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.WindowInsets
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.navigationBars
import androidx.compose.foundation.layout.padding 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.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.graphics.Brush import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.rememberVectorPainter
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
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.R 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.data.model.User
import fr.iut.alldev.allin.ext.asPaddingValues
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.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 @Composable
fun FriendsScreenContent( fun FriendsScreenContent(
friends: List<User>, addTabState: FriendsScreenViewModel.AddTabState,
deletedUsers: List<String>,
requestedUsers: List<String>,
search: String, search: String,
onToggleDeleteFriend: (User) -> Unit, onToggleDeleteFriend: (User) -> Unit,
setSearch: (String) -> Unit, setSearch: (String) -> Unit,
requestsTabState: FriendsScreenViewModel.RequestsTabState,
acceptRequest: (User) -> Unit,
declineRequest: (User) -> Unit
) { ) {
LazyColumn( val focus = LocalFocusManager.current
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()
)
}
stickyHeader { Column(Modifier.fillMaxSize()) {
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) { Text(
FriendsScreenLine( text = stringResource(id = R.string.friends_title),
username = it.username, style = AllInTheme.typography.h1,
status = if (it.id in deletedUsers) { color = AllInColorToken.allInGrey,
FriendStatus.NOT_FRIEND fontSize = 24.sp,
} else if (it.id in requestedUsers) { textAlign = TextAlign.Center,
FriendStatus.REQUESTED modifier = Modifier
} else it.friendStatus ?: FriendStatus.NOT_FRIEND, .fillMaxWidth()
toggleIsFriend = { onToggleDeleteFriend(it) } .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() { private fun FriendsScreenContentPreview() {
AllInTheme { AllInTheme {
FriendsScreenContent( FriendsScreenContent(
friends = emptyList(), addTabState = FriendsScreenViewModel.AddTabState.Loaded(emptyList()),
deletedUsers = emptyList(),
requestedUsers = emptyList(),
search = "", search = "",
setSearch = {}, 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) { when (val event = it) {
is ToConfirmBet -> { is ToConfirmBet -> {
Timber.d("ToConfirmBet") Timber.d("ToConfirmBet")
scope.launch { eventBottomSheetState.show() }
event.Display(sheetState = eventBottomSheetState) { event.Display(sheetState = eventBottomSheetState) {
mainViewModel.dismissedEvents += it mainViewModel.dismissedEvents += it
scope.launch { scope.launch {
@ -193,6 +194,7 @@ fun MainScreen(
is WonBet -> { is WonBet -> {
Timber.d("WonBet") Timber.d("WonBet")
scope.launch { eventBottomSheetState.show() }
event.Display(sheetState = eventBottomSheetState) { event.Display(sheetState = eventBottomSheetState) {
mainViewModel.dismissedEvents += it mainViewModel.dismissedEvents += it
scope.launch { scope.launch {

@ -168,6 +168,11 @@
<!--Friends--> <!--Friends-->
<string name="friends_title">Amis</string> <string name="friends_title">Amis</string>
<string name="friends_request_sent">Requête envoyée</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--> <!--Daily reward-->
<string name="daily_reward_title">Récompense quotidienne</string> <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> <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--> <!--Friends-->
<string name="friends_title">Friends</string> <string name="friends_title">Friends</string>
<string name="friends_request_sent">Request sent</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--> <!--Daily reward-->
<string name="daily_reward_title">Daily reward</string> <string name="daily_reward_title">Daily reward</string>

@ -48,6 +48,9 @@ interface AllInApi {
@GET("friends/gets") @GET("friends/gets")
suspend fun getFriends(@Header("Authorization") token: String): List<ResponseUser> suspend fun getFriends(@Header("Authorization") token: String): List<ResponseUser>
@GET("friends/requests")
suspend fun getFriendRequests(@Header("Authorization") token: String): List<ResponseUser>
@POST("friends/add") @POST("friends/add")
suspend fun addFriend( suspend fun addFriend(
@Header("Authorization") token: String, @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) { override suspend fun addFriend(token: String, request: RequestFriend) {
val user = getUserFromToken(token) ?: throw MockAllInApiException("Invalid login/password.") val user = getUserFromToken(token) ?: throw MockAllInApiException("Invalid login/password.")
val requestUser = val requestUser =

@ -4,6 +4,7 @@ import fr.iut.alldev.allin.data.model.User
abstract class FriendRepository { abstract class FriendRepository {
abstract suspend fun getFriends(token: String): List<User> 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 add(token: String, username: String)
abstract suspend fun remove(token: String, username: String) abstract suspend fun remove(token: String, username: String)
abstract suspend fun searchNew(token: String, search: String): List<User> 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() } 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) { override suspend fun add(token: String, username: String) {
return api.addFriend( return api.addFriend(
token = token.formatBearerToken(), token = token.formatBearerToken(),

Loading…
Cancel
Save