From 74a6f77261661728761380ff21c54fae25fed211 Mon Sep 17 00:00:00 2001 From: tdpeuter Date: Mon, 15 May 2023 14:14:49 +0200 Subject: [PATCH] Add friends search screen --- .../friends_search/SearchFriendUiState.kt | 10 + .../friends_search/SearchFriendsScreen.kt | 269 ++++++++++++++++++ .../friends_search/SearchFriendsViewModel.kt | 57 ++++ 3 files changed, 336 insertions(+) create mode 100644 app/src/main/java/be/ugent/sel/studeez/screens/friends/friends_search/SearchFriendUiState.kt create mode 100644 app/src/main/java/be/ugent/sel/studeez/screens/friends/friends_search/SearchFriendsScreen.kt create mode 100644 app/src/main/java/be/ugent/sel/studeez/screens/friends/friends_search/SearchFriendsViewModel.kt diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/friends/friends_search/SearchFriendUiState.kt b/app/src/main/java/be/ugent/sel/studeez/screens/friends/friends_search/SearchFriendUiState.kt new file mode 100644 index 0000000..0a5a10f --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/friends/friends_search/SearchFriendUiState.kt @@ -0,0 +1,10 @@ +package be.ugent.sel.studeez.screens.friends.friends_search + +import be.ugent.sel.studeez.data.local.models.User +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow + +data class SearchFriendUiState( + val queryString: String = "", + val searchResults: Flow> = emptyFlow() +) \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/friends/friends_search/SearchFriendsScreen.kt b/app/src/main/java/be/ugent/sel/studeez/screens/friends/friends_search/SearchFriendsScreen.kt new file mode 100644 index 0000000..a91d0f4 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/friends/friends_search/SearchFriendsScreen.kt @@ -0,0 +1,269 @@ +package be.ugent.sel.studeez.screens.friends.friends_search + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.Person +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +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 be.ugent.sel.studeez.R +import be.ugent.sel.studeez.common.composable.SearchField +import be.ugent.sel.studeez.common.composable.drawer.DrawerEntry +import be.ugent.sel.studeez.data.local.models.User +import be.ugent.sel.studeez.resources +import be.ugent.sel.studeez.ui.theme.StudeezTheme +import kotlinx.coroutines.flow.* +import be.ugent.sel.studeez.R.string as AppText + +data class SearchFriendsActions( + val onQueryStringChange: (String) -> Unit, + val getUsersWithUsername: (String) -> Unit, + val getAllUsers: () -> Flow>, + val goToProfile: (String) -> Unit +) + +fun getSearchFriendsActions( + viewModel: SearchFriendsViewModel, + open: (String) -> Unit +): SearchFriendsActions { + return SearchFriendsActions( + onQueryStringChange = viewModel::onQueryStringChange, + getUsersWithUsername = viewModel::getUsersWithUsername, + getAllUsers = { viewModel.getAllUsers() }, + goToProfile = { userId -> viewModel.goToProfile(userId, open) } + ) +} + +@Composable +fun SearchFriendsRoute( + popUp: () -> Unit, + open: (String) -> Unit, + viewModel: SearchFriendsViewModel +) { + val uiState by viewModel.uiState + + SearchFriendsScreen( + popUp = popUp, + uiState = uiState, + searchFriendsActions = getSearchFriendsActions( + viewModel = viewModel, + open = open + ) + ) +} + +@Composable +fun SearchFriendsScreen( + popUp: () -> Unit, + uiState: SearchFriendUiState, + searchFriendsActions: SearchFriendsActions +) { + var query by remember { mutableStateOf(uiState.queryString) } + val searchResults = searchFriendsActions.getAllUsers().collectAsState( + initial = emptyList() + ) + + Scaffold( + topBar = { + TopAppBar( + title = { + SearchField( + value = query, + onValueChange = { newValue -> + searchFriendsActions.onQueryStringChange(newValue) + query = newValue + }, + onSubmit = { }, + label = AppText.search_friends + ) + }, + navigationIcon = { + IconButton(onClick = popUp) { + Icon( + imageVector = Icons.Default.ArrowBack, + contentDescription = resources().getString(R.string.go_back) + ) + } + } + ) + } + ) { paddingValues -> + LazyColumn( + modifier = Modifier.padding(paddingValues) + ) { + items (searchResults.value) { user -> + UserEntry( + user = user, + goToProfile = searchFriendsActions.goToProfile + ) + } + } + } +} + +@Preview +@Composable +fun SearchFriendsPreview() { + StudeezTheme { + SearchFriendsScreen( + popUp = {}, + uiState = SearchFriendUiState( + queryString = "dit is een test", + searchResults = flowOf(listOf(User( + id = "someid", + username = "Eerste user", + biography = "blah blah blah" + ))) + ), + searchFriendsActions = SearchFriendsActions( + onQueryStringChange = {}, + getUsersWithUsername = {}, + getAllUsers = { + flowOf(listOf(User( + id = "someid", + username = "Eerste user", + biography = "blah blah blah" + ))) + }, + goToProfile = { } + ) + ) + } +} + +@Composable +fun UserEntry( + user: User, + goToProfile: (String) -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + ) { + Box( + modifier = Modifier + .fillMaxWidth(0.15f) + .background(MaterialTheme.colors.primary, CircleShape) + ) { + Icon( + painter = painterResource(id = R.drawable.ic_visibility_on), + contentDescription = null, + modifier = Modifier + .fillMaxHeight() + .align(Alignment.Center), + tint = MaterialTheme.colors.onPrimary + ) + } + + Box ( + modifier = Modifier + .fillMaxWidth(0.65f) + ) { + Column ( + modifier = Modifier + .padding(vertical = 4.dp) + ) { + Text( + text = user.username, + fontSize = 16.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Text( + text = "${resources().getString(AppText.app_name)} ${resources().getString(AppText.friend)}", + fontSize = 14.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } + + Box( + modifier = Modifier.fillMaxWidth(0.15f) + ) { + SearchFriendsDropDown( + user = user, + goToProfile = goToProfile + ) + } + } +} + +@Preview +@Composable +fun UserEntryPreview() { + StudeezTheme { + UserEntry( + user = User( + id = "someid", + username = "Eerste user", + biography = "blah blah blah" + ), + goToProfile = { } + ) + } +} + +/** + * Three dots that open a dropdown menu that allow to go the users profile. + */ +@Composable +fun SearchFriendsDropDown( + user: User, + goToProfile: (String) -> Unit +) { + var expanded by remember { mutableStateOf(false) } + + IconButton( + onClick = { expanded = true } + ) { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_more_horizontal), + contentDescription = stringResource(AppText.view_more), + modifier = Modifier.fillMaxSize() + ) + } + + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + DropdownMenuItem(onClick = { expanded = false }) { + DrawerEntry( + icon = Icons.Default.Person, + text = stringResource(id = AppText.show_profile) + ) { + goToProfile(user.id) + } + } + } +} + +@Preview +@Composable +fun SearchFriendsDropDownPreview() { + StudeezTheme { + SearchFriendsDropDown( + user = User( + id = "someid", + username = "Eerste user", + biography = "blah blah blah" + ), + goToProfile = { } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/be/ugent/sel/studeez/screens/friends/friends_search/SearchFriendsViewModel.kt b/app/src/main/java/be/ugent/sel/studeez/screens/friends/friends_search/SearchFriendsViewModel.kt new file mode 100644 index 0000000..a25e1a7 --- /dev/null +++ b/app/src/main/java/be/ugent/sel/studeez/screens/friends/friends_search/SearchFriendsViewModel.kt @@ -0,0 +1,57 @@ +package be.ugent.sel.studeez.screens.friends.friends_search + +import androidx.compose.runtime.mutableStateOf +import be.ugent.sel.studeez.data.local.models.User +import be.ugent.sel.studeez.data.remote.FirebaseUser +import be.ugent.sel.studeez.domain.LogService +import be.ugent.sel.studeez.domain.UserDAO +import be.ugent.sel.studeez.navigation.StudeezDestinations +import be.ugent.sel.studeez.screens.StudeezViewModel +import be.ugent.sel.studeez.screens.profile.public_profile.SelectedProfileState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +@HiltViewModel +class SearchFriendsViewModel @Inject constructor( + private val userDAO: UserDAO, + private val selectedProfileState: SelectedProfileState, + logService: LogService +): StudeezViewModel(logService) { + + var uiState = mutableStateOf(SearchFriendUiState()) + private set + + fun onQueryStringChange(newValue: String) { + uiState.value = uiState.value.copy( + queryString = newValue + ) + uiState.value = uiState.value.copy( + searchResults = userDAO.getUsersWithQuery( + fieldName = FirebaseUser.USERNAME, + value = uiState.value.queryString + ) + ) + } + + fun getUsersWithUsername( + value: String + ): Flow> { + return userDAO.getUsersWithQuery( + fieldName = FirebaseUser.USERNAME, + value = value + ) + } + + fun getAllUsers(): Flow> { + return userDAO.getAllUsers() + } + + fun goToProfile( + userId: String, + open: (String) -> Unit + ) { + selectedProfileState.selectedUserId = userId + open(StudeezDestinations.PUBLIC_PROFILE_SCREEN) + } +} \ No newline at end of file