#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,28 +53,41 @@ 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 {
|
||||||
LabelledInputField(
|
item {
|
||||||
value = uiState.username,
|
LabelledInputField(
|
||||||
onNewValue = editProfileActions.onUserNameChange,
|
value = uiState.username,
|
||||||
label = R.string.username
|
onNewValue = editProfileActions.onUserNameChange,
|
||||||
)
|
label = AppText.username
|
||||||
BasicTextButton(
|
)
|
||||||
text = R.string.save,
|
}
|
||||||
Modifier.textButton(),
|
item {
|
||||||
action = {
|
LabelledInputField(
|
||||||
editProfileActions.onSaveClick()
|
value = uiState.biography,
|
||||||
goBack()
|
onNewValue = editProfileActions.onBiographyChange,
|
||||||
}
|
label = AppText.biography
|
||||||
)
|
)
|
||||||
BasicTextButton(
|
}
|
||||||
text = R.string.delete_profile,
|
item {
|
||||||
Modifier.textButton(),
|
BasicTextButton(
|
||||||
action = editProfileActions.onDeleteClick
|
text = AppText.save,
|
||||||
)
|
Modifier.textButton(),
|
||||||
|
action = {
|
||||||
|
editProfileActions.onSaveClick()
|
||||||
|
goBack()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
BasicTextButton(
|
||||||
|
text = AppText.delete_profile,
|
||||||
|
Modifier.textButton(),
|
||||||
|
action = editProfileActions.onDeleteClick
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,6 +96,6 @@ fun EditProfileScreen(
|
||||||
@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