diff --git a/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/CommentInput.kt b/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/CommentInput.kt new file mode 100644 index 0000000..4bf6d47 --- /dev/null +++ b/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/CommentInput.kt @@ -0,0 +1,57 @@ +package io.openfeedback.android.m3 + +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Send +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.tooling.preview.Preview +import io.openfeedback.android.R + +@Composable +fun CommentInput( + value: String, + onValueChange: (String) -> Unit, + onSubmit: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true +) { + TextField( + value = value, + onValueChange = onValueChange, + modifier = modifier, + label = { Text(text = stringResource(id = R.string.openfeedback_comments_title_input)) }, + trailingIcon = { + IconButton(onClick = onSubmit) { + Icon( + imageVector = Icons.Outlined.Send, + contentDescription = stringResource(id = R.string.openfeedback_comments_send) + ) + } + }, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { onSubmit() }), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + maxLines = 5 + ) +} + +@Preview +@Composable +private fun CommentInputPreview() { + MaterialTheme { + CommentInput( + value = "My comment", + onValueChange = {}, + onSubmit = {} + ) + } +} diff --git a/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/CommentItems.kt b/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/CommentItems.kt index e94a10d..b7ce32e 100644 --- a/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/CommentItems.kt +++ b/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/CommentItems.kt @@ -19,6 +19,7 @@ internal fun CommentItems( comments: List, modifier: Modifier = Modifier, verticalArrangement: Arrangement.Vertical = Arrangement.spacedBy(8.dp), + commentInput: @Composable ColumnScope.() -> Unit, comment: @Composable ColumnScope.(UIComment) -> Unit ) { Column( @@ -29,6 +30,7 @@ internal fun CommentItems( text = stringResource(id = R.string.openfeedback_comments_title), style = MaterialTheme.typography.titleMedium ) + commentInput() comments.forEachIndexed { index, uiComment -> comment(uiComment) } @@ -60,6 +62,9 @@ private fun VoteItemsPreview() { votedByUser = true ) ), + commentInput = { + CommentInput(value = "", onValueChange = {}, onSubmit = {}) + }, comment = { Comment(comment = it, onClick = {}) } diff --git a/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/OpenFeedbackLayout.kt b/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/OpenFeedbackLayout.kt index 15dd902..4bf8c6c 100644 --- a/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/OpenFeedbackLayout.kt +++ b/openfeedback-m3/src/main/kotlin/io/openfeedback/android/m3/OpenFeedbackLayout.kt @@ -60,6 +60,14 @@ fun OpenFeedback( onClick = viewModel::upVote ) }, + commentInput = { + CommentInput( + value = session.commentValue, + onValueChange = viewModel::valueChangedComment, + onSubmit = viewModel::submitComment, + modifier = Modifier.fillMaxWidth() + ) + }, content = { VoteCard( voteModel = it, @@ -83,6 +91,7 @@ fun OpenFeedbackLayout( horizontalArrangement: Arrangement.Horizontal = Arrangement.spacedBy(8.dp), verticalArrangement: Arrangement.Vertical = Arrangement.spacedBy(8.dp), comment: @Composable ColumnScope.(UIComment) -> Unit, + commentInput: @Composable ColumnScope.() -> Unit, content: @Composable ColumnScope.(UIVoteItem) -> Unit ) { Column( @@ -100,6 +109,7 @@ fun OpenFeedbackLayout( CommentItems( comments = sessionFeedback.comments, verticalArrangement = verticalArrangement, + commentInput = commentInput, comment = comment ) Spacer(modifier = Modifier.height(8.dp)) @@ -119,6 +129,8 @@ private fun OpenFeedbackLayoutPreview() { MaterialTheme { OpenFeedbackLayout( sessionFeedback = UISessionFeedback( + commentValue = "", + commentVoteItemId = "", comments = listOf( UIComment( id = "", @@ -156,6 +168,9 @@ private fun OpenFeedbackLayoutPreview() { ), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp), + commentInput = { + CommentInput(value = "", onValueChange = {}, onSubmit = {}) + }, comment = { Comment(comment = it, onClick = {}) } ) { VoteCard( diff --git a/openfeedback-viewmodel/src/main/java/io/openfeedback/android/viewmodels/OpenFeedbackViewModel.kt b/openfeedback-viewmodel/src/main/java/io/openfeedback/android/viewmodels/OpenFeedbackViewModel.kt index ed32530..178619f 100644 --- a/openfeedback-viewmodel/src/main/java/io/openfeedback/android/viewmodels/OpenFeedbackViewModel.kt +++ b/openfeedback-viewmodel/src/main/java/io/openfeedback/android/viewmodels/OpenFeedbackViewModel.kt @@ -63,6 +63,25 @@ class OpenFeedbackViewModel( } } + fun valueChangedComment(value: String) { + if (_uiState.value !is OpenFeedbackUiState.Success) return + val session = (_uiState.value as OpenFeedbackUiState.Success).session + _uiState.value = OpenFeedbackUiState.Success(session.copy(commentValue = value)) + } + + fun submitComment() = viewModelScope.launch { + if (_uiState.value !is OpenFeedbackUiState.Success) return@launch + val session = (_uiState.value as OpenFeedbackUiState.Success).session + if (session.commentVoteItemId == null) return@launch + repository.newComment( + projectId = projectId, + talkId = sessionId, + voteItemId = session.commentVoteItemId, + status = VoteStatus.Active, + text = session.commentValue + ) + } + fun vote(voteItem: UIVoteItem) = viewModelScope.launch { repository.setVote( projectId = projectId, diff --git a/openfeedback-viewmodel/src/main/java/io/openfeedback/android/viewmodels/mappers/UiMappers.kt b/openfeedback-viewmodel/src/main/java/io/openfeedback/android/viewmodels/mappers/UiMappers.kt index 4692663..b8adf28 100644 --- a/openfeedback-viewmodel/src/main/java/io/openfeedback/android/viewmodels/mappers/UiMappers.kt +++ b/openfeedback-viewmodel/src/main/java/io/openfeedback/android/viewmodels/mappers/UiMappers.kt @@ -20,6 +20,8 @@ fun convertToUiSessionFeedback( ): UISessionFeedback { val formatter = SimpleDateFormat("dd MMMM yyyy, hh:mm", locale) return UISessionFeedback( + commentValue = "", + commentVoteItemId = project.voteItems.find { it.type == "text" }?.id, comments = totalVotes.comments.map { commentItem -> UIComment( id = commentItem.value.id, @@ -48,6 +50,8 @@ fun convertToUiSessionFeedback( fun UISessionFeedbackWithColors.convertToUiSessionFeedback( oldSessionFeedback: UISessionFeedback? ): UISessionFeedback = UISessionFeedback( + commentValue = oldSessionFeedback?.commentValue ?: "", + commentVoteItemId = oldSessionFeedback?.commentVoteItemId ?: this.session.commentVoteItemId, comments = this.session.comments.map { newCommentItem -> val oldCommentItem = oldSessionFeedback?.comments?.find { it.id == newCommentItem.id } val newDots = if (oldCommentItem != null) { diff --git a/openfeedback-viewmodel/src/main/java/io/openfeedback/android/viewmodels/models/UISessionFeedback.kt b/openfeedback-viewmodel/src/main/java/io/openfeedback/android/viewmodels/models/UISessionFeedback.kt index 86b7dc6..36fde14 100644 --- a/openfeedback-viewmodel/src/main/java/io/openfeedback/android/viewmodels/models/UISessionFeedback.kt +++ b/openfeedback-viewmodel/src/main/java/io/openfeedback/android/viewmodels/models/UISessionFeedback.kt @@ -4,6 +4,8 @@ import androidx.compose.runtime.Immutable @Immutable data class UISessionFeedback( + val commentValue: String, + val commentVoteItemId: String?, val comments: List, val voteItem: List ) diff --git a/openfeedback/src/main/java/io/openfeedback/android/OpenFeedbackRepository.kt b/openfeedback/src/main/java/io/openfeedback/android/OpenFeedbackRepository.kt index 380ed67..c51bf89 100644 --- a/openfeedback/src/main/java/io/openfeedback/android/OpenFeedbackRepository.kt +++ b/openfeedback/src/main/java/io/openfeedback/android/OpenFeedbackRepository.kt @@ -39,6 +39,12 @@ class OpenFeedbackRepository( optimisticVoteCaching.votes ) + suspend fun newComment(projectId: String, talkId: String, voteItemId: String, status: VoteStatus, text: String) { + auth.withFirebaseUser { + firestore.newComment(projectId, it.uid, talkId, voteItemId, status, text) + } + } + suspend fun setVote(projectId: String, talkId: String, voteItemId: String, status: VoteStatus) { auth.withFirebaseUser { optimisticVoteCaching.updateVotes(voteItemId, status) diff --git a/openfeedback/src/main/java/io/openfeedback/android/sources/OpenFeedbackFirestore.kt b/openfeedback/src/main/java/io/openfeedback/android/sources/OpenFeedbackFirestore.kt index 46a8356..1ed7e5d 100644 --- a/openfeedback/src/main/java/io/openfeedback/android/sources/OpenFeedbackFirestore.kt +++ b/openfeedback/src/main/java/io/openfeedback/android/sources/OpenFeedbackFirestore.kt @@ -52,6 +52,7 @@ class OpenFeedbackFirestore( ?.map { val voteItemId = it.key (it.value as HashMap<*, *>).entries + .filter { (it.value as Map).isNotEmpty() } .map { entry -> entry.key as String to (entry.value as Map) .convertToModel( @@ -66,6 +67,50 @@ class OpenFeedbackFirestore( ) } + suspend fun newComment( + projectId: String, + userId: String, + talkId: String, + voteItemId: String, + status: VoteStatus, + text: String + ) { + if (text.trim() == "") return + val collectionReference = firestore.collection("projects/$projectId/userVotes") + val querySnapshot = collectionReference + .whereEqualTo("userId", userId) + .whereEqualTo("talkId", talkId) + .whereEqualTo("voteItemId", voteItemId) + .get() + .await() + if (querySnapshot.isEmpty) { + val documentReference = collectionReference.document() + documentReference.set( + mapOf( + "id" to documentReference.id, + "createdAt" to Date(), + "projectId" to projectId, + "status" to status.value, + "talkId" to talkId, + "updatedAt" to Date(), + "userId" to userId, + "voteItemId" to voteItemId, + "text" to text.trim() + ) + ) + } else { + collectionReference + .document(querySnapshot.documents[0].id) + .update( + mapOf( + "updatedAt" to Date(), + "status" to status.value, + "text" to text.trim() + ) + ) + } + } + suspend fun setVote( projectId: String, userId: String, diff --git a/openfeedback/src/main/res/values-fr/strings.xml b/openfeedback/src/main/res/values-fr/strings.xml index d78c9ba..6672c1a 100644 --- a/openfeedback/src/main/res/values-fr/strings.xml +++ b/openfeedback/src/main/res/values-fr/strings.xml @@ -1,6 +1,8 @@ Commentaires + Votre commentaire + Soumettre votre commentaire %d votes Proposé par \ No newline at end of file diff --git a/openfeedback/src/main/res/values/strings.xml b/openfeedback/src/main/res/values/strings.xml index 203f844..ace1841 100644 --- a/openfeedback/src/main/res/values/strings.xml +++ b/openfeedback/src/main/res/values/strings.xml @@ -1,6 +1,8 @@ Comments + Your comment + Submit comment %d votes Powered by \ No newline at end of file