Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Communication: Improve auto completion for tagging users and channels #76

Merged
merged 7 commits into from
Nov 15, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ val md_theme_dark_inverseSurface = Color(0xFFE2E2E5)
val md_theme_dark_inversePrimary = Color(0xFF006398)
val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFF93CCFF)
val md_theme_dark_outlineVariant = Color(0xFF42474E)
val md_theme_dark_outlineVariant = Color(0xFF55515B)
val md_theme_dark_scrim = Color(0xFF000000)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ internal open class ConversationViewModel(
val exerciseAutoCompleteItems =
course
.exercises
.filter { query in it.title.orEmpty() }
.filter { it.title.orEmpty().contains(query, ignoreCase = true) }
.mapNotNull { exercise ->
val exerciseTag = when (exercise) {
is FileUploadExercise -> "file-upload"
Expand Down Expand Up @@ -538,7 +538,7 @@ internal open class ConversationViewModel(
conversationsDataState.bind { conversations ->
val conversationAutoCompleteItems = conversations
.filterIsInstance<ChannelChat>()
.filter { query in it.name }
.filter { it.name.contains(query, ignoreCase = true) }
.map { channel ->
AutoCompleteHint(
hint = channel.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,36 @@ import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Divider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.min
import androidx.compose.ui.util.fastForEachIndexed
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupPositionProvider
import androidx.compose.ui.window.PopupProperties
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.R

const val TEST_TAG_REPLY_AUTO_COMPLETE_POPUP_LIST = "TEST_TAG_REPLY_AUTO_COMPLETE_POPUP_LIST"

private val HintHorizontalPadding = 16.dp
private val PopupVerticalPadding = 8.dp
private val AutoCompletionDialogMaxHeight = 270.dp

@Composable
internal fun ReplyAutoCompletePopup(
popupPositionProvider: PopupPositionProvider,
targetWidth: Dp,
maxHeight: Dp,
maxHeightFromScreen: Dp,
autoCompleteCategories: List<AutoCompleteCategory>,
performAutoComplete: (replacement: String) -> Unit,
onDismissRequest: () -> Unit
Expand All @@ -44,9 +50,11 @@ internal fun ReplyAutoCompletePopup(
properties = PopupProperties(dismissOnClickOutside = true),
onDismissRequest = onDismissRequest
) {
val maxHeight = min(maxHeightFromScreen, AutoCompletionDialogMaxHeight)
ReplyAutoCompletePopupBody(
modifier = Modifier
.heightIn(max = maxHeight)
.padding(vertical = PopupVerticalPadding)
.heightIn(max = maxHeight - PopupVerticalPadding * 2)
.width(targetWidth),
autoCompleteCategories = autoCompleteCategories,
performAutoComplete = performAutoComplete
Expand All @@ -62,12 +70,12 @@ private fun ReplyAutoCompletePopupBody(
) {
LazyColumn(
modifier = modifier
.background(
color = MaterialTheme.colorScheme.surfaceVariant,
shape = RoundedCornerShape(topStart = 45f, topEnd = 45f)
)
.padding(top = 8.dp)
.clip(MaterialTheme.shapes.large)
.background(color = MaterialTheme.colorScheme.surfaceVariant)
.padding(8.dp)
.testTag(TEST_TAG_REPLY_AUTO_COMPLETE_POPUP_LIST)
) {

autoCompleteCategories.forEachIndexed { categoryIndex, category ->
item {
AutoCompleteCategoryComposable(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,37 @@ package de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.ui

import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Cancel
import androidx.compose.material.icons.filled.Done
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Send
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
Expand Down Expand Up @@ -58,7 +80,7 @@ internal fun ReplyTextField(
Surface(
modifier = modifier.defaultMinSize(minHeight = 48.dp),
color = MaterialTheme.colorScheme.surfaceVariant,
shape = RoundedCornerShape(5)
shape = MaterialTheme.shapes.large
) {
Box(
modifier = Modifier
Expand Down Expand Up @@ -173,14 +195,16 @@ private fun CreateReplyUi(
val autoCompleteHints = manageAutoCompleteHints(currentTextFieldValue)

var textFieldWidth by remember { mutableIntStateOf(0) }
var popupMaxHeight by remember { mutableStateOf(0) }
var popupMaxHeight by remember { mutableIntStateOf(0) }

if (autoCompleteHints.orEmpty().flatMap { it.items }
.isNotEmpty() && mayShowAutoCompletePopup) {
val showAutoCompletePopup = mayShowAutoCompletePopup
&& autoCompleteHints.orEmpty().flatMap { it.items }.isNotEmpty()

if (showAutoCompletePopup) {
ReplyAutoCompletePopup(
autoCompleteCategories = autoCompleteHints.orEmpty(),
targetWidth = with(LocalDensity.current) { textFieldWidth.toDp() },
maxHeight = with(LocalDensity.current) { popupMaxHeight.toDp() },
maxHeightFromScreen = with(LocalDensity.current) { popupMaxHeight.toDp() },
popupPositionProvider = ReplyAutoCompletePopupPositionProvider,
performAutoComplete = { replacement ->
replyMode.onUpdate(
Expand All @@ -201,11 +225,11 @@ private fun CreateReplyUi(
modifier = Modifier
.fillMaxWidth()
.onSizeChanged { textFieldWidth = it.width }
.padding(vertical = 8.dp, horizontal = 8.dp)
.onGloballyPositioned { coordinates ->
val textFieldWindowTopLeft = coordinates.localToWindow(Offset.Zero)
popupMaxHeight = textFieldWindowTopLeft.y.toInt()
val textFieldRootTopLeft = coordinates.localToRoot(Offset.Zero)
popupMaxHeight = textFieldRootTopLeft.y.toInt()
}
.padding(8.dp)
.testTag(TEST_TAG_REPLY_TEXT_FIELD),
textFieldValue = currentTextFieldValue,
onTextChanged = replyMode::onUpdate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performTextInput
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollToIndex
import androidx.compose.ui.test.performTextClearance
import androidx.compose.ui.test.performTextInput
import androidx.compose.ui.text.input.TextFieldValue
import androidx.test.ext.junit.runners.AndroidJUnit4
import de.tum.informatics.www1.artemis.native_app.core.data.DataState
Expand Down Expand Up @@ -140,7 +141,9 @@ class ReplyTextFieldUiTest {

private fun ComposeContentTestRule.assertAllAutoCompletionHintsShown() {
onNodeWithText("User1").assertExists()
onNodeWithTag(TEST_TAG_REPLY_AUTO_COMPLETE_POPUP_LIST).performScrollToIndex(1)
onNodeWithText("User2").assertExists()
onNodeWithTag(TEST_TAG_REPLY_AUTO_COMPLETE_POPUP_LIST).performScrollToIndex(2)
onNodeWithText("User3").assertExists()
}
}
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[versions]
compileSdk = "35"
targetSdk = "34"
minSdk = "31"
minSdk = "30"
buildToolsVersion = "35.0.0"

accompanist = "0.30.1"
Expand Down
Loading