Basic friends overview screen
This commit is contained in:
		
							parent
							
								
									3b50054ff5
								
							
						
					
					
						commit
						87fe476724
					
				
					 4 changed files with 266 additions and 0 deletions
				
			
		|  | @ -0,0 +1,218 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.friend | ||||||
|  | 
 | ||||||
|  | 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.Icon | ||||||
|  | import androidx.compose.material.IconButton | ||||||
|  | import androidx.compose.material.MaterialTheme | ||||||
|  | import androidx.compose.material.Text | ||||||
|  | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.runtime.collectAsState | ||||||
|  | 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.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.BasicButton | ||||||
|  | import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate | ||||||
|  | import be.ugent.sel.studeez.common.ext.basicButton | ||||||
|  | import be.ugent.sel.studeez.data.local.models.Friendship | ||||||
|  | import be.ugent.sel.studeez.data.local.models.User | ||||||
|  | import be.ugent.sel.studeez.resources | ||||||
|  | import be.ugent.sel.studeez.ui.theme.StudeezTheme | ||||||
|  | import com.google.firebase.Timestamp | ||||||
|  | import kotlinx.coroutines.flow.Flow | ||||||
|  | import kotlinx.coroutines.flow.emptyFlow | ||||||
|  | import be.ugent.sel.studeez.R.string as AppText | ||||||
|  | 
 | ||||||
|  | data class FriendsOverviewActions( | ||||||
|  |     val getFriendsFlow: () -> Flow<List<Pair<User, Friendship>>>, | ||||||
|  |     val searchFriends: () -> Unit | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | fun getFriendsOverviewActions( | ||||||
|  |     viewModel: FriendsOverviewViewModel, | ||||||
|  |     open: (String) -> Unit | ||||||
|  | ): FriendsOverviewActions { | ||||||
|  |     return FriendsOverviewActions( | ||||||
|  |         getFriendsFlow = viewModel::getAllFriends, | ||||||
|  |         searchFriends = { viewModel.searchFriends(open) } | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun FriendsOveriewRoute( | ||||||
|  |     open: (String) -> Unit, | ||||||
|  |     popUp: () -> Unit, | ||||||
|  |     viewModel: FriendsOverviewViewModel | ||||||
|  | ) { | ||||||
|  |     FriendsOverviewScreen( | ||||||
|  |         friendsOverviewActions = getFriendsOverviewActions( | ||||||
|  |             viewModel = viewModel, | ||||||
|  |             open = open | ||||||
|  |         ), | ||||||
|  |         popUp = popUp | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun FriendsOverviewScreen( | ||||||
|  |     friendsOverviewActions: FriendsOverviewActions, | ||||||
|  |     popUp: () -> Unit | ||||||
|  | ) { | ||||||
|  |     val friends = friendsOverviewActions.getFriendsFlow().collectAsState(initial = emptyList()) | ||||||
|  |      | ||||||
|  |     SecondaryScreenTemplate( | ||||||
|  |         title = "TODO there needs to be a search field here", // TODO | ||||||
|  |         popUp = popUp | ||||||
|  |     ) { | ||||||
|  |         LazyColumn { | ||||||
|  |             if (friends.value.isEmpty()) { | ||||||
|  |                 // Show a quick button to search friends when the user does not have any friends yet. | ||||||
|  |                 item { | ||||||
|  |                     BasicButton( | ||||||
|  |                         text = AppText.no_friends, | ||||||
|  |                         modifier = Modifier.basicButton() | ||||||
|  |                     ) { | ||||||
|  |                         friendsOverviewActions.searchFriends() | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             items(friends.value) { friend -> | ||||||
|  |                 FriendsEntry( | ||||||
|  |                     user = friend.first, | ||||||
|  |                     friendship = friend.second | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun FriendsOverviewPreview() { | ||||||
|  |     StudeezTheme { | ||||||
|  |         FriendsOverviewScreen( | ||||||
|  |             friendsOverviewActions = FriendsOverviewActions( | ||||||
|  |                 getFriendsFlow = { emptyFlow() }, | ||||||
|  |                 searchFriends = {} | ||||||
|  |             ), | ||||||
|  |             popUp = {} | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun FriendsEntry( | ||||||
|  |     user: User, | ||||||
|  |     friendship: Friendship | ||||||
|  | ) { | ||||||
|  |     // TODO Styling | ||||||
|  |     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) | ||||||
|  |         ) { | ||||||
|  |             ThreeDots(friendship = friendship) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun FriendsEntryPreview() { | ||||||
|  |     StudeezTheme { | ||||||
|  |         FriendsEntry( | ||||||
|  |             user = User( | ||||||
|  |                 id = "", | ||||||
|  |                 username = "Tibo De Peuter", | ||||||
|  |                 biography = "short bio" | ||||||
|  |             ), | ||||||
|  |             friendship = Friendship( | ||||||
|  |                 id = "", | ||||||
|  |                 friendId = "someId", | ||||||
|  |                 friendsSince = Timestamp.now(), | ||||||
|  |                 accepted = true | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Composable | ||||||
|  | fun ThreeDots( | ||||||
|  |     friendship: Friendship | ||||||
|  | ) { | ||||||
|  |     IconButton( | ||||||
|  |         onClick = { /* TODO Open dropdown */ } | ||||||
|  |     ) { | ||||||
|  |         Icon( | ||||||
|  |             imageVector = ImageVector.vectorResource(id = R.drawable.ic_more_horizontal), | ||||||
|  |             contentDescription = resources().getString(AppText.view_more), | ||||||
|  |             modifier = Modifier.fillMaxSize() | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Preview | ||||||
|  | @Composable | ||||||
|  | fun ThreeDotsPreview() { | ||||||
|  |     StudeezTheme { | ||||||
|  |         ThreeDots( | ||||||
|  |             friendship = Friendship( | ||||||
|  |                 id = "", | ||||||
|  |                 friendId = "someId", | ||||||
|  |                 friendsSince = Timestamp.now(), | ||||||
|  |                 accepted = true | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,41 @@ | ||||||
|  | package be.ugent.sel.studeez.screens.friend | ||||||
|  | 
 | ||||||
|  | import be.ugent.sel.studeez.data.local.models.Friendship | ||||||
|  | import be.ugent.sel.studeez.data.local.models.User | ||||||
|  | import be.ugent.sel.studeez.domain.FriendshipDAO | ||||||
|  | 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 dagger.hilt.android.lifecycle.HiltViewModel | ||||||
|  | import kotlinx.coroutines.flow.Flow | ||||||
|  | import kotlinx.coroutines.flow.combine | ||||||
|  | import kotlinx.coroutines.flow.flatMapConcat | ||||||
|  | import javax.inject.Inject | ||||||
|  | 
 | ||||||
|  | @HiltViewModel | ||||||
|  | class FriendsOverviewViewModel @Inject constructor( | ||||||
|  |     private val userDAO: UserDAO, | ||||||
|  |     private val friendshipDAO: FriendshipDAO, | ||||||
|  |     logService: LogService | ||||||
|  | ) : StudeezViewModel(logService) { | ||||||
|  | 
 | ||||||
|  |     fun getAllFriends(): Flow<List<Pair<User, Friendship>>> { | ||||||
|  |         return friendshipDAO.getAllFriendships() | ||||||
|  |             .flatMapConcat { friendships -> | ||||||
|  |                 val userFlows = friendships.map { friendship -> | ||||||
|  |                     userDAO.getUserDetails(friendship.friendId) | ||||||
|  |                 } | ||||||
|  |                 combine(userFlows) { users -> | ||||||
|  |                     friendships.zip(users) { friendship, user -> | ||||||
|  |                         Pair(user, friendship) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun searchFriends(open: (String) -> Unit) { | ||||||
|  |         open(StudeezDestinations.SEARCH_FRIENDS_SCREEN) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										5
									
								
								app/src/main/res/drawable/ic_more_horizontal.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/src/main/res/drawable/ic_more_horizontal.xml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | <vector android:height="24dp" android:tint="#000000" | ||||||
|  |     android:viewportHeight="24" android:viewportWidth="24" | ||||||
|  |     android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> | ||||||
|  |     <path android:fillColor="@android:color/white" android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/> | ||||||
|  | </vector> | ||||||
|  | @ -16,6 +16,7 @@ | ||||||
|     <string name="go_back">Go back</string> |     <string name="go_back">Go back</string> | ||||||
|     <string name="next">Next</string> |     <string name="next">Next</string> | ||||||
|     <string name="start">Start</string> |     <string name="start">Start</string> | ||||||
|  |     <string name="view_more">View more</string> | ||||||
| 
 | 
 | ||||||
|     <!-- Messages --> |     <!-- Messages --> | ||||||
|     <string name="success">Success!</string> |     <string name="success">Success!</string> | ||||||
|  | @ -112,6 +113,7 @@ | ||||||
|         <item quantity="other">%d Friends</item> |         <item quantity="other">%d Friends</item> | ||||||
|     </plurals> |     </plurals> | ||||||
|     <string name="add_friend_not_possible_yet">Adding friends still needs to be implemented. Hang on tight!</string> <!-- TODO Remove this description line once implemented. --> |     <string name="add_friend_not_possible_yet">Adding friends still needs to be implemented. Hang on tight!</string> <!-- TODO Remove this description line once implemented. --> | ||||||
|  |     <string name="no_friends">You don\'t have any friends yet. Add one!</string> | ||||||
| 
 | 
 | ||||||
|     <!-- ========== Create & edit screens ========== --> |     <!-- ========== Create & edit screens ========== --> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Reference in a new issue