mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-22 21:52:28 +00:00
android, desktop: group member mentions (#5574)
* initial wip * initial parser * limit mentions * wip types and ohter changes * small animation * better limit * show mentioned member when mention is in selectable area * better space handling * animation working * changes * auto tagging * centralize state * focus in desktop fix * close picker on click outside * use profile display name, avoid local * show box with max number of mentions * scrollbar in group mentions desktop * sending and displaying mentions in views based on latest core code * latest types and updates new api * desktop selection area fix * show mentions correctly * new notifications UI changes * local alias support * mention notifications working * mentions markdown changes * fix notifications * Revert "fix notifications" This reverts commit 59643c24725d3caee3c629df6732f4b5bc294f8f. * simple cleanup * mentions in info view * refactor/renames * show member name to replies of my messages as primary * show local alias and display name for mentions * show 4 rows and almost all of 5th as picker max height * only call list members api on new @ and searchn in all names * fix * correction * fixes * unread mentions chat stats * unread indication in chat * filtering of unread * show @ in chat previews * @ style * alone @ * forgotten change * deleted * remove whitespace * fix to make clear chat mark tags red * comments changes * @ as icon to avoid issues * change * simplify like ios * renames * wip using haskell parser * show mention name containing @ in quotes * cleanup and position of cursor after replace * move * show selected tick and edits working * cimention in map * eol * text selection * refactor --------- Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com> Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
This commit is contained in:
29 files changed
+830
-225
No files matched your search
+17
-16
@@ -10,8 +10,7 @@ import androidx.compose.material.TextFieldDefaults.textFieldWithLabelPadding
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.*
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.input.key.*
|
||||
@@ -51,19 +50,21 @@ actual fun PlatformTextField(
|
||||
userIsObserver: Boolean,
|
||||
placeholder: String,
|
||||
showVoiceButton: Boolean,
|
||||
onMessageChange: (String) -> Unit,
|
||||
onMessageChange: (ComposeMessage) -> Unit,
|
||||
onUpArrow: () -> Unit,
|
||||
onFilesPasted: (List<URI>) -> Unit,
|
||||
focusRequester: FocusRequester?,
|
||||
onDone: () -> Unit,
|
||||
) {
|
||||
|
||||
val cs = composeState.value
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val focusManager = LocalFocusManager.current
|
||||
val keyboard = LocalSoftwareKeyboardController.current
|
||||
val focusReq = focusRequester ?: remember { FocusRequester() }
|
||||
LaunchedEffect(cs.contextItem) {
|
||||
if (cs.contextItem !is ComposeContextItem.QuotedItem) return@LaunchedEffect
|
||||
// In replying state
|
||||
focusRequester.requestFocus()
|
||||
focusReq.requestFocus()
|
||||
delay(50)
|
||||
keyboard?.show()
|
||||
}
|
||||
@@ -74,9 +75,9 @@ actual fun PlatformTextField(
|
||||
keyboard?.hide()
|
||||
}
|
||||
}
|
||||
val lastTimeWasRtlByCharacters = remember { mutableStateOf(isRtl(cs.message.subSequence(0, min(50, cs.message.length)))) }
|
||||
val lastTimeWasRtlByCharacters = remember { mutableStateOf(isRtl(cs.message.text.subSequence(0, min(50, cs.message.text.length)))) }
|
||||
val isRtlByCharacters = remember(cs.message) {
|
||||
if (cs.message.isNotEmpty()) isRtl(cs.message.subSequence(0, min(50, cs.message.length))) else lastTimeWasRtlByCharacters.value
|
||||
if (cs.message.text.isNotEmpty()) isRtl(cs.message.text.subSequence(0, min(50, cs.message.text.length))) else lastTimeWasRtlByCharacters.value
|
||||
}
|
||||
LaunchedEffect(isRtlByCharacters) {
|
||||
lastTimeWasRtlByCharacters.value = isRtlByCharacters
|
||||
@@ -84,12 +85,12 @@ actual fun PlatformTextField(
|
||||
val isLtrGlobally = LocalLayoutDirection.current == LayoutDirection.Ltr
|
||||
// Different padding here is for a text that is considered RTL with non-RTL locale set globally.
|
||||
// In this case padding from right side should be bigger
|
||||
val startEndPadding = if (cs.message.isEmpty() && showVoiceButton && isRtlByCharacters && isLtrGlobally) 95.dp else 50.dp
|
||||
val startEndPadding = if (cs.message.text.isEmpty() && showVoiceButton && isRtlByCharacters && isLtrGlobally) 95.dp else 50.dp
|
||||
val startPadding = if (isRtlByCharacters && isLtrGlobally) startEndPadding else 0.dp
|
||||
val endPadding = if (isRtlByCharacters && isLtrGlobally) 0.dp else startEndPadding
|
||||
val padding = PaddingValues(startPadding, 12.dp, endPadding, 0.dp)
|
||||
var textFieldValueState by remember { mutableStateOf(TextFieldValue(text = cs.message)) }
|
||||
val textFieldValue = textFieldValueState.copy(text = cs.message)
|
||||
var textFieldValueState by remember { mutableStateOf(TextFieldValue(text = cs.message.text, selection = cs.message.selection)) }
|
||||
val textFieldValue = textFieldValueState.copy(text = cs.message.text, selection = cs.message.selection)
|
||||
val clipboard = LocalClipboardManager.current
|
||||
BasicTextField(
|
||||
value = textFieldValue,
|
||||
@@ -105,7 +106,7 @@ actual fun PlatformTextField(
|
||||
}
|
||||
}
|
||||
textFieldValueState = it
|
||||
onMessageChange(it.text)
|
||||
onMessageChange(ComposeMessage(it.text, it.selection))
|
||||
}
|
||||
},
|
||||
textStyle = textStyle.value,
|
||||
@@ -118,7 +119,7 @@ actual fun PlatformTextField(
|
||||
.padding(start = startPadding, end = endPadding)
|
||||
.offset(y = (-5).dp)
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester)
|
||||
.focusRequester(focusReq)
|
||||
.onPreviewKeyEvent {
|
||||
if ((it.key == Key.Enter || it.key == Key.NumPadEnter) && it.type == KeyEventType.KeyDown) {
|
||||
if (it.isShiftPressed) {
|
||||
@@ -129,12 +130,12 @@ actual fun PlatformTextField(
|
||||
text = newText,
|
||||
selection = TextRange(textFieldValue.selection.min + 1)
|
||||
)
|
||||
onMessageChange(newText)
|
||||
onMessageChange(ComposeMessage(newText, textFieldValueState.selection))
|
||||
} else if (!sendMsgButtonDisabled) {
|
||||
onDone()
|
||||
}
|
||||
true
|
||||
} else if (it.key == Key.DirectionUp && it.type == KeyEventType.KeyDown && cs.message.isEmpty()) {
|
||||
} else if (it.key == Key.DirectionUp && it.type == KeyEventType.KeyDown && cs.message.text.isEmpty()) {
|
||||
onUpArrow()
|
||||
true
|
||||
} else if (it.key == Key.V &&
|
||||
@@ -166,7 +167,7 @@ actual fun PlatformTextField(
|
||||
chatModel.filesToDelete.add(tempFile)
|
||||
|
||||
tempFile.writeBytes(bytes)
|
||||
composeState.processPickedMedia(listOf(tempFile.toURI()), composeState.value.message)
|
||||
composeState.processPickedMedia(listOf(tempFile.toURI()), composeState.value.message.text)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Pasting image exception: ${e.stackTraceToString()}")
|
||||
@@ -200,7 +201,7 @@ actual fun PlatformTextField(
|
||||
}
|
||||
}
|
||||
)
|
||||
showDeleteTextButton.value = cs.message.split("\n").size >= 4 && !cs.inProgress
|
||||
showDeleteTextButton.value = cs.message.text.split("\n").size >= 4 && !cs.inProgress
|
||||
if (composeState.value.preview is ComposePreview.VoicePreview) {
|
||||
ComposeOverlay(MR.strings.voice_message_send_text, textStyle, padding)
|
||||
} else if (userIsObserver) {
|
||||
|
||||
+7
-3
@@ -111,7 +111,9 @@ actual fun LazyColumnWithScrollBarNoAppBar(
|
||||
additionalBarOffset: State<Dp>?,
|
||||
additionalTopBar: State<Boolean>,
|
||||
chatBottomBar: State<Boolean>,
|
||||
content: LazyListScope.() -> Unit
|
||||
maxHeight: State<Dp>?,
|
||||
containerAlignment: Alignment,
|
||||
content: LazyListScope.() -> Unit,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val scrollBarAlpha = remember { Animatable(0f) }
|
||||
@@ -135,9 +137,11 @@ actual fun LazyColumnWithScrollBarNoAppBar(
|
||||
// When scroll bar is dragging, there is no scroll event in nested scroll modifier. So, listen for changes on lazy column state
|
||||
// (only first visible row is useful because LazyColumn doesn't have absolute scroll position, only relative to row)
|
||||
val scrollBarDraggingState = remember { mutableStateOf(false) }
|
||||
Box {
|
||||
Box(contentAlignment = containerAlignment) {
|
||||
LazyColumn(modifier.then(scrollModifier), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content)
|
||||
ScrollBar(reverseLayout, state, scrollBarAlpha, scrollJob, scrollBarDraggingState, additionalBarOffset, additionalTopBar, chatBottomBar)
|
||||
Box(if (maxHeight?.value != null) Modifier.height(maxHeight.value).fillMaxWidth() else Modifier.fillMaxSize(), contentAlignment = Alignment.CenterEnd) {
|
||||
DesktopScrollBar(rememberScrollbarAdapter(state), Modifier.fillMaxHeight(), scrollBarAlpha, scrollJob, reverseLayout, scrollBarDraggingState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user