#63 Add bio to profile

This commit is contained in:
Tibo De Peuter 2023-05-10 12:13:48 +02:00
parent 33b8a32330
commit 81e777f21d
10 changed files with 124 additions and 48 deletions

View file

@ -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 = ""
)

View file

@ -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

View file

@ -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() {

View file

@ -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({}, {}, {}, {}))
} }
} }

View file

@ -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 = ""
) )

View file

@ -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)
} }
} }

View file

@ -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 }, {}, {}, {}, {}, {}, {}, {})
) )

View file

@ -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) {

View file

@ -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)
} }
} }

View file

@ -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 ========== -->