#63 Add bio to profile
This commit is contained in:
		
							parent
							
								
									33b8a32330
								
							
						
					
					
						commit
						81e777f21d
					
				
					 10 changed files with 124 additions and 48 deletions
				
			
		|  | @ -1,3 +1,9 @@ | ||||||
| package be.ugent.sel.studeez.data.local.models | package be.ugent.sel.studeez.data.local.models | ||||||
| 
 | 
 | ||||||
| data class User(val id: String = "") | import com.google.firebase.firestore.DocumentId | ||||||
|  | 
 | ||||||
|  | data class User( | ||||||
|  |     @DocumentId val id: String = "", | ||||||
|  |     val username: String = "", | ||||||
|  |     val biography: String = "" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | @ -1,9 +1,15 @@ | ||||||
| package be.ugent.sel.studeez.domain | package be.ugent.sel.studeez.domain | ||||||
| 
 | 
 | ||||||
|  | import be.ugent.sel.studeez.data.local.models.User | ||||||
|  | 
 | ||||||
| interface UserDAO { | interface UserDAO { | ||||||
| 
 | 
 | ||||||
|     suspend fun getUsername(): String? |     suspend fun getUser(): User | ||||||
|     suspend fun save(newUsername: String) | 
 | ||||||
|  |     suspend fun saveUser( | ||||||
|  |         newUsername: String, | ||||||
|  |         newBiography: String = "" | ||||||
|  |     ) | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Delete all references to this user in the database. Similar to the deleteCascade in |      * Delete all references to this user in the database. Similar to the deleteCascade in | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ package be.ugent.sel.studeez.domain.implementation | ||||||
| 
 | 
 | ||||||
| import be.ugent.sel.studeez.R | import be.ugent.sel.studeez.R | ||||||
| import be.ugent.sel.studeez.common.snackbar.SnackbarManager | import be.ugent.sel.studeez.common.snackbar.SnackbarManager | ||||||
|  | import be.ugent.sel.studeez.data.local.models.User | ||||||
| import be.ugent.sel.studeez.domain.AccountDAO | import be.ugent.sel.studeez.domain.AccountDAO | ||||||
| import be.ugent.sel.studeez.domain.UserDAO | import be.ugent.sel.studeez.domain.UserDAO | ||||||
| import com.google.firebase.firestore.DocumentReference | import com.google.firebase.firestore.DocumentReference | ||||||
|  | @ -14,12 +15,22 @@ class FirebaseUserDAO @Inject constructor( | ||||||
|     private val auth: AccountDAO |     private val auth: AccountDAO | ||||||
|     ) : UserDAO { |     ) : UserDAO { | ||||||
| 
 | 
 | ||||||
|     override suspend fun getUsername(): String? { |     override suspend fun getUser(): User { | ||||||
|         return currentUserDocument().get().await().getString("username") |         val userDocument = currentUserDocument().get().await() | ||||||
|  |         return User( | ||||||
|  |             username = userDocument.getString(USERNAME_FIELD) ?: "", | ||||||
|  |             biography = userDocument.getString(BIOGRAPHY_FIELD) ?: "" | ||||||
|  |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override suspend fun save(newUsername: String) { |     override suspend fun saveUser( | ||||||
|         currentUserDocument().set(mapOf("username" to newUsername)) |         newUsername: String, | ||||||
|  |         newBiography: String | ||||||
|  |     ) { | ||||||
|  |         currentUserDocument().set(mapOf( | ||||||
|  |             USERNAME_FIELD to newUsername, | ||||||
|  |             BIOGRAPHY_FIELD to newBiography | ||||||
|  |         )) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun currentUserDocument(): DocumentReference = |     private fun currentUserDocument(): DocumentReference = | ||||||
|  | @ -27,6 +38,8 @@ class FirebaseUserDAO @Inject constructor( | ||||||
| 
 | 
 | ||||||
|     companion object { |     companion object { | ||||||
|         private const val USER_COLLECTION = "users" |         private const val USER_COLLECTION = "users" | ||||||
|  |         private const val USERNAME_FIELD = "username" | ||||||
|  |         private const val BIOGRAPHY_FIELD = "biography" | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override suspend fun deleteUserReferences() { |     override suspend fun deleteUserReferences() { | ||||||
|  |  | ||||||
|  | @ -1,20 +1,21 @@ | ||||||
| package be.ugent.sel.studeez.screens.profile | package be.ugent.sel.studeez.screens.profile | ||||||
| 
 | 
 | ||||||
| import androidx.compose.foundation.layout.Column | 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.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
| import androidx.compose.ui.tooling.preview.Preview | import androidx.compose.ui.tooling.preview.Preview | ||||||
| import be.ugent.sel.studeez.R |  | ||||||
| import be.ugent.sel.studeez.common.composable.BasicTextButton | import be.ugent.sel.studeez.common.composable.BasicTextButton | ||||||
| import be.ugent.sel.studeez.common.composable.LabelledInputField | import be.ugent.sel.studeez.common.composable.LabelledInputField | ||||||
| import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate | import be.ugent.sel.studeez.common.composable.SecondaryScreenTemplate | ||||||
| import be.ugent.sel.studeez.common.ext.textButton | import be.ugent.sel.studeez.common.ext.textButton | ||||||
| import be.ugent.sel.studeez.resources | import be.ugent.sel.studeez.resources | ||||||
| import be.ugent.sel.studeez.ui.theme.StudeezTheme | import be.ugent.sel.studeez.ui.theme.StudeezTheme | ||||||
|  | import be.ugent.sel.studeez.R.string as AppText | ||||||
| 
 | 
 | ||||||
| data class EditProfileActions( | data class EditProfileActions( | ||||||
|     val onUserNameChange: (String) -> Unit, |     val onUserNameChange: (String) -> Unit, | ||||||
|  |     val onBiographyChange: (String) -> Unit, | ||||||
|     val onSaveClick: () -> Unit, |     val onSaveClick: () -> Unit, | ||||||
|     val onDeleteClick: () -> Unit |     val onDeleteClick: () -> Unit | ||||||
| ) | ) | ||||||
|  | @ -25,6 +26,7 @@ fun getEditProfileActions( | ||||||
| ): EditProfileActions { | ): EditProfileActions { | ||||||
|     return EditProfileActions( |     return EditProfileActions( | ||||||
|         onUserNameChange = { viewModel.onUsernameChange(it) }, |         onUserNameChange = { viewModel.onUsernameChange(it) }, | ||||||
|  |         onBiographyChange = { viewModel.onBiographyChange(it) }, | ||||||
|         onSaveClick = { viewModel.onSaveClick() }, |         onSaveClick = { viewModel.onSaveClick() }, | ||||||
|         onDeleteClick = { viewModel.onDeleteClick(openAndPopUp) }, |         onDeleteClick = { viewModel.onDeleteClick(openAndPopUp) }, | ||||||
|     ) |     ) | ||||||
|  | @ -51,36 +53,49 @@ fun EditProfileScreen( | ||||||
|     editProfileActions: EditProfileActions, |     editProfileActions: EditProfileActions, | ||||||
| ) { | ) { | ||||||
|     SecondaryScreenTemplate( |     SecondaryScreenTemplate( | ||||||
|         title = resources().getString(R.string.editing_profile), |         title = resources().getString(AppText.editing_profile), | ||||||
|         popUp = goBack |         popUp = goBack | ||||||
|     ) { |     ) { | ||||||
|         Column { |         LazyColumn { | ||||||
|  |             item { | ||||||
|                 LabelledInputField( |                 LabelledInputField( | ||||||
|                     value = uiState.username, |                     value = uiState.username, | ||||||
|                     onNewValue = editProfileActions.onUserNameChange, |                     onNewValue = editProfileActions.onUserNameChange, | ||||||
|                 label = R.string.username |                     label = AppText.username | ||||||
|                 ) |                 ) | ||||||
|  |             } | ||||||
|  |             item { | ||||||
|  |                 LabelledInputField( | ||||||
|  |                     value = uiState.biography, | ||||||
|  |                     onNewValue = editProfileActions.onBiographyChange, | ||||||
|  |                     label = AppText.biography | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |             item { | ||||||
|                 BasicTextButton( |                 BasicTextButton( | ||||||
|                 text = R.string.save, |                     text = AppText.save, | ||||||
|                     Modifier.textButton(), |                     Modifier.textButton(), | ||||||
|                     action = { |                     action = { | ||||||
|                         editProfileActions.onSaveClick() |                         editProfileActions.onSaveClick() | ||||||
|                         goBack() |                         goBack() | ||||||
|                     } |                     } | ||||||
|                 ) |                 ) | ||||||
|  |             } | ||||||
|  |             item { | ||||||
|                  BasicTextButton( |                  BasicTextButton( | ||||||
|                 text = R.string.delete_profile, |                     text = AppText.delete_profile, | ||||||
|                     Modifier.textButton(), |                     Modifier.textButton(), | ||||||
|                     action = editProfileActions.onDeleteClick |                     action = editProfileActions.onDeleteClick | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @Preview | @Preview | ||||||
| @Composable | @Composable | ||||||
| fun EditProfileScreenComposable() { | fun EditProfileScreenComposable() { | ||||||
|     StudeezTheme { |     StudeezTheme { | ||||||
|         EditProfileScreen({}, ProfileEditUiState(), EditProfileActions({}, {}, {})) |         EditProfileScreen({}, ProfileEditUiState(), EditProfileActions({}, {}, {}, {})) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| package be.ugent.sel.studeez.screens.profile | package be.ugent.sel.studeez.screens.profile | ||||||
| 
 | 
 | ||||||
| data class ProfileEditUiState ( | data class ProfileEditUiState ( | ||||||
|     val username: String = "" |     val username: String = "", | ||||||
|  |     val biography: String = "" | ||||||
| ) | ) | ||||||
|  | @ -3,6 +3,7 @@ package be.ugent.sel.studeez.screens.profile | ||||||
| import androidx.compose.runtime.mutableStateOf | import androidx.compose.runtime.mutableStateOf | ||||||
| import be.ugent.sel.studeez.R | import be.ugent.sel.studeez.R | ||||||
| import be.ugent.sel.studeez.common.snackbar.SnackbarManager | import be.ugent.sel.studeez.common.snackbar.SnackbarManager | ||||||
|  | import be.ugent.sel.studeez.data.local.models.User | ||||||
| import be.ugent.sel.studeez.domain.AccountDAO | import be.ugent.sel.studeez.domain.AccountDAO | ||||||
| import be.ugent.sel.studeez.domain.LogService | import be.ugent.sel.studeez.domain.LogService | ||||||
| import be.ugent.sel.studeez.domain.UserDAO | import be.ugent.sel.studeez.domain.UserDAO | ||||||
|  | @ -23,7 +24,11 @@ class ProfileEditViewModel @Inject constructor( | ||||||
| 
 | 
 | ||||||
|     init { |     init { | ||||||
|         launchCatching { |         launchCatching { | ||||||
|             uiState.value = uiState.value.copy(username = userDAO.getUsername()!!) |             val user: User = userDAO.getUser() | ||||||
|  |             uiState.value = uiState.value.copy( | ||||||
|  |                 username = user.username, | ||||||
|  |                 biography = user.biography | ||||||
|  |             ) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -31,9 +36,16 @@ class ProfileEditViewModel @Inject constructor( | ||||||
|         uiState.value = uiState.value.copy(username = newValue) |         uiState.value = uiState.value.copy(username = newValue) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     fun onBiographyChange(newValue: String) { | ||||||
|  |         uiState.value = uiState.value.copy(biography = newValue) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     fun onSaveClick() { |     fun onSaveClick() { | ||||||
|         launchCatching { |         launchCatching { | ||||||
|             userDAO.save(uiState.value.username) |             userDAO.saveUser( | ||||||
|  |                 newUsername = uiState.value.username, | ||||||
|  |                 newBiography = uiState.value.biography | ||||||
|  |             ) | ||||||
|             SnackbarManager.showMessage(R.string.success) |             SnackbarManager.showMessage(R.string.success) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,17 +1,18 @@ | ||||||
| package be.ugent.sel.studeez.screens.profile | package be.ugent.sel.studeez.screens.profile | ||||||
| 
 | 
 | ||||||
|  | import androidx.compose.foundation.layout.Arrangement | ||||||
|  | import androidx.compose.foundation.layout.padding | ||||||
|  | import androidx.compose.foundation.lazy.LazyColumn | ||||||
| import androidx.compose.material.Icon | import androidx.compose.material.Icon | ||||||
| import androidx.compose.material.IconButton | import androidx.compose.material.IconButton | ||||||
|  | import androidx.compose.material.Text | ||||||
| import androidx.compose.material.icons.Icons | import androidx.compose.material.icons.Icons | ||||||
| import androidx.compose.material.icons.filled.Edit | import androidx.compose.material.icons.filled.Edit | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.* | ||||||
| import androidx.compose.runtime.LaunchedEffect | import androidx.compose.ui.Modifier | ||||||
| import androidx.compose.runtime.getValue | import androidx.compose.ui.text.style.TextAlign | ||||||
| import androidx.compose.runtime.mutableStateOf |  | ||||||
| import androidx.compose.runtime.remember |  | ||||||
| import androidx.compose.runtime.setValue |  | ||||||
| import androidx.compose.ui.tooling.preview.Preview | import androidx.compose.ui.tooling.preview.Preview | ||||||
| import be.ugent.sel.studeez.R | import androidx.compose.ui.unit.dp | ||||||
| import be.ugent.sel.studeez.common.composable.Headline | import be.ugent.sel.studeez.common.composable.Headline | ||||||
| import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate | import be.ugent.sel.studeez.common.composable.PrimaryScreenTemplate | ||||||
| import be.ugent.sel.studeez.common.composable.drawer.DrawerActions | import be.ugent.sel.studeez.common.composable.drawer.DrawerActions | ||||||
|  | @ -22,16 +23,18 @@ import be.ugent.sel.studeez.R.string as AppText | ||||||
| 
 | 
 | ||||||
| data class ProfileActions( | data class ProfileActions( | ||||||
|     val getUsername: suspend CoroutineScope.() -> String?, |     val getUsername: suspend CoroutineScope.() -> String?, | ||||||
|     val onEditProfileClick: () -> Unit, |     val getBiography: suspend CoroutineScope.() -> String?, | ||||||
|  |     val onEditProfileClick: () -> Unit | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| fun getProfileActions( | fun getProfileActions( | ||||||
|     viewModel: ProfileViewModel, |     viewModel: ProfileViewModel, | ||||||
|     open: (String) -> Unit, |     open: (String) -> Unit | ||||||
| ): ProfileActions { | ): ProfileActions { | ||||||
|     return ProfileActions( |     return ProfileActions( | ||||||
|         getUsername = { viewModel.getUsername() }, |         getUsername = { viewModel.getUsername() }, | ||||||
|         onEditProfileClick = { viewModel.onEditProfileClick(open) }, |         getBiography = { viewModel.getBiography() }, | ||||||
|  |         onEditProfileClick = { viewModel.onEditProfileClick(open) } | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -56,8 +59,10 @@ fun ProfileScreen( | ||||||
|     navigationBarActions: NavigationBarActions, |     navigationBarActions: NavigationBarActions, | ||||||
| ) { | ) { | ||||||
|     var username: String? by remember { mutableStateOf("") } |     var username: String? by remember { mutableStateOf("") } | ||||||
|  |     var biography: String? by remember { mutableStateOf("") } | ||||||
|     LaunchedEffect(key1 = Unit) { |     LaunchedEffect(key1 = Unit) { | ||||||
|         username = profileActions.getUsername(this) |         username = profileActions.getUsername(this) | ||||||
|  |         biography = profileActions.getBiography(this) | ||||||
|     } |     } | ||||||
|     PrimaryScreenTemplate( |     PrimaryScreenTemplate( | ||||||
|         title = resources().getString(AppText.profile), |         title = resources().getString(AppText.profile), | ||||||
|  | @ -65,7 +70,20 @@ fun ProfileScreen( | ||||||
|         navigationBarActions = navigationBarActions, |         navigationBarActions = navigationBarActions, | ||||||
|         barAction = { EditAction(onClick = profileActions.onEditProfileClick) } |         barAction = { EditAction(onClick = profileActions.onEditProfileClick) } | ||||||
|     ) { |     ) { | ||||||
|         Headline(text = (username ?: resources().getString(R.string.no_username))) |         LazyColumn( | ||||||
|  |             verticalArrangement = Arrangement.spacedBy(15.dp) | ||||||
|  |         ) { | ||||||
|  |             item { | ||||||
|  |                 Headline(text = username ?: resources().getString(AppText.no_username)) | ||||||
|  |             } | ||||||
|  |             item { | ||||||
|  |                 Text( | ||||||
|  |                     text = biography ?: "", | ||||||
|  |                     textAlign = TextAlign.Center, | ||||||
|  |                     modifier = Modifier.padding(48.dp, 0.dp) | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -86,7 +104,7 @@ fun EditAction( | ||||||
| @Composable | @Composable | ||||||
| fun ProfileScreenPreview() { | fun ProfileScreenPreview() { | ||||||
|     ProfileScreen( |     ProfileScreen( | ||||||
|         profileActions = ProfileActions({ null }, {}), |         profileActions = ProfileActions({ null }, { null }, {}), | ||||||
|         drawerActions = DrawerActions({}, {}, {}, {}, {}), |         drawerActions = DrawerActions({}, {}, {}, {}, {}), | ||||||
|         navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}) |         navigationBarActions = NavigationBarActions({ false }, {}, {}, {}, {}, {}, {}, {}) | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  | @ -13,8 +13,12 @@ class ProfileViewModel @Inject constructor( | ||||||
|     logService: LogService |     logService: LogService | ||||||
| ) : StudeezViewModel(logService) { | ) : StudeezViewModel(logService) { | ||||||
| 
 | 
 | ||||||
|     suspend fun getUsername(): String? { |     suspend fun getUsername(): String { | ||||||
|         return userDAO.getUsername() |         return userDAO.getUser().username | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     suspend fun getBiography(): String { | ||||||
|  |         return userDAO.getUser().biography | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun onEditProfileClick(open: (String) -> Unit) { |     fun onEditProfileClick(open: (String) -> Unit) { | ||||||
|  |  | ||||||
|  | @ -66,7 +66,7 @@ class SignUpViewModel @Inject constructor( | ||||||
|         launchCatching { |         launchCatching { | ||||||
|             accountDAO.signUpWithEmailAndPassword(email, password) |             accountDAO.signUpWithEmailAndPassword(email, password) | ||||||
|             accountDAO.signInWithEmailAndPassword(email, password) |             accountDAO.signInWithEmailAndPassword(email, password) | ||||||
|             userDAO.save(username) |             userDAO.saveUser(username) | ||||||
|             openAndPopUp(HOME_SCREEN, SIGN_UP_SCREEN) |             openAndPopUp(HOME_SCREEN, SIGN_UP_SCREEN) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -54,6 +54,7 @@ | ||||||
|     <string name="edit_profile">Edit profile</string> |     <string name="edit_profile">Edit profile</string> | ||||||
|     <string name="editing_profile">Editing profile</string> |     <string name="editing_profile">Editing profile</string> | ||||||
|     <string name="delete_profile">Delete profile</string> |     <string name="delete_profile">Delete profile</string> | ||||||
|  |     <string name="biography">Bio</string> | ||||||
| 
 | 
 | ||||||
|     <!-- ========== Drawer ========== --> |     <!-- ========== Drawer ========== --> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Reference in a new issue