Add friends search screen
This commit is contained in:
		
							parent
							
								
									7c95e78b2a
								
							
						
					
					
						commit
						74a6f77261
					
				
					 3 changed files with 336 additions and 0 deletions
				
			
		|  | @ -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<List<User>> = emptyFlow() | ||||||
|  | ) | ||||||
|  | @ -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<List<User>>, | ||||||
|  |     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 = { } | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -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<List<User>> { | ||||||
|  |         return userDAO.getUsersWithQuery( | ||||||
|  |             fieldName = FirebaseUser.USERNAME, | ||||||
|  |             value = value | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun getAllUsers(): Flow<List<User>> { | ||||||
|  |         return userDAO.getAllUsers() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun goToProfile( | ||||||
|  |         userId: String, | ||||||
|  |         open: (String) -> Unit | ||||||
|  |     ) { | ||||||
|  |         selectedProfileState.selectedUserId = userId | ||||||
|  |         open(StudeezDestinations.PUBLIC_PROFILE_SCREEN) | ||||||
|  |     } | ||||||
|  | } | ||||||
		Reference in a new issue