feat: mention autocomplete
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
924568a7b3
commit
6ac347ef07
|
|
@ -38,4 +38,10 @@ class Members {
|
||||||
member.nickname?.let { userId to member.nickname }
|
member.nickname?.let { userId to member.nickname }
|
||||||
}?.toMap() ?: emptyMap()
|
}?.toMap() ?: emptyMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun filterNamesFor(serverId: String, query: String): List<Member> {
|
||||||
|
return memberCache[serverId]?.values?.filter { member ->
|
||||||
|
member.nickname?.contains(query, ignoreCase = true) ?: false
|
||||||
|
} ?: emptyList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,8 @@ import chat.revolt.api.REVOLT_FILES
|
||||||
import chat.revolt.api.schemas.ChannelType
|
import chat.revolt.api.schemas.ChannelType
|
||||||
import chat.revolt.api.schemas.Member
|
import chat.revolt.api.schemas.Member
|
||||||
import chat.revolt.components.generic.RemoteImage
|
import chat.revolt.components.generic.RemoteImage
|
||||||
|
import chat.revolt.components.generic.UserAvatar
|
||||||
|
import chat.revolt.components.screens.chat.ChannelIcon
|
||||||
import chat.revolt.internals.Autocomplete
|
import chat.revolt.internals.Autocomplete
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
@ -155,9 +157,9 @@ fun NativeMessageField(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
forceSendButton: Boolean = false,
|
forceSendButton: Boolean = false,
|
||||||
disabled: Boolean = false,
|
disabled: Boolean = false,
|
||||||
editMode: Boolean = false,
|
|
||||||
serverId: String? = null,
|
serverId: String? = null,
|
||||||
channelId: String? = null,
|
channelId: String? = null,
|
||||||
|
editMode: Boolean = false,
|
||||||
cancelEdit: () -> Unit = {},
|
cancelEdit: () -> Unit = {},
|
||||||
onFocusChange: (Boolean) -> Unit = {},
|
onFocusChange: (Boolean) -> Unit = {},
|
||||||
onSelectionChange: (Pair<Int, Int>) -> Unit = {}
|
onSelectionChange: (Pair<Int, Int>) -> Unit = {}
|
||||||
|
|
@ -247,10 +249,15 @@ fun NativeMessageField(
|
||||||
},
|
},
|
||||||
label = { Text("@${item.user.username}#${item.user.discriminator}") },
|
label = { Text("@${item.user.username}#${item.user.discriminator}") },
|
||||||
icon = {
|
icon = {
|
||||||
Icon(
|
UserAvatar(
|
||||||
painter = painterResource(R.drawable.ic_human_greeting_variant_24dp),
|
username = item.user.username
|
||||||
contentDescription = null,
|
?: stringResource(R.string.unknown),
|
||||||
modifier = Modifier.size(SuggestionChipDefaults.IconSize)
|
userId = item.user.id ?: "",
|
||||||
|
avatar = item.user.avatar,
|
||||||
|
rawUrl = item.member?.avatar?.id?.let {
|
||||||
|
"$REVOLT_FILES/avatars/$it?max_side=64"
|
||||||
|
},
|
||||||
|
size = SuggestionChipDefaults.IconSize,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -270,11 +277,7 @@ fun NativeMessageField(
|
||||||
},
|
},
|
||||||
label = { Text("#${item.channel.name}") },
|
label = { Text("#${item.channel.name}") },
|
||||||
icon = {
|
icon = {
|
||||||
Icon(
|
item.channel.channelType?.let { type -> ChannelIcon(channelType = type) }
|
||||||
painter = painterResource(R.drawable.ic_pound_24dp),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(SuggestionChipDefaults.IconSize)
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.animateItemPlacement()
|
.animateItemPlacement()
|
||||||
|
|
@ -360,6 +363,8 @@ fun NativeMessageField(
|
||||||
AndroidView(
|
AndroidView(
|
||||||
factory = { context ->
|
factory = { context ->
|
||||||
object : androidx.appcompat.widget.AppCompatEditText(context) {
|
object : androidx.appcompat.widget.AppCompatEditText(context) {
|
||||||
|
var serverId: String? = null
|
||||||
|
|
||||||
override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection? {
|
override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection? {
|
||||||
var ic = super.onCreateInputConnection(outAttrs)
|
var ic = super.onCreateInputConnection(outAttrs)
|
||||||
EditorInfoCompat.setContentMimeTypes(
|
EditorInfoCompat.setContentMimeTypes(
|
||||||
|
|
@ -402,6 +407,17 @@ fun NativeMessageField(
|
||||||
Autocomplete.emoji(lastWord.substring(1))
|
Autocomplete.emoji(lastWord.substring(1))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastWord.startsWith('@') -> {
|
||||||
|
if (channelId == null) return
|
||||||
|
autocompleteSuggestions.addAll(
|
||||||
|
Autocomplete.user(
|
||||||
|
channelId,
|
||||||
|
this.serverId,
|
||||||
|
lastWord.substring(1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.apply {
|
}.apply {
|
||||||
|
|
@ -489,6 +505,7 @@ fun NativeMessageField(
|
||||||
it.setSelection(value.length)
|
it.setSelection(value.length)
|
||||||
}
|
}
|
||||||
it.hint = it.context.getString(placeholderResource, channelName)
|
it.hint = it.context.getString(placeholderResource, channelName)
|
||||||
|
it.serverId = serverId
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package chat.revolt.internals
|
package chat.revolt.internals
|
||||||
|
|
||||||
import chat.revolt.api.RevoltAPI
|
import chat.revolt.api.RevoltAPI
|
||||||
|
import chat.revolt.api.schemas.ChannelType
|
||||||
import chat.revolt.components.chat.AutocompleteSuggestion
|
import chat.revolt.components.chat.AutocompleteSuggestion
|
||||||
|
|
||||||
object Autocomplete {
|
object Autocomplete {
|
||||||
|
|
@ -33,4 +34,104 @@ object Autocomplete {
|
||||||
|
|
||||||
return (unicodeResults + customResults)
|
return (unicodeResults + customResults)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun user(
|
||||||
|
channelId: String,
|
||||||
|
serverId: String? = null,
|
||||||
|
query: String
|
||||||
|
): List<AutocompleteSuggestion.User> {
|
||||||
|
val channel = RevoltAPI.channelCache[channelId] ?: return emptyList()
|
||||||
|
|
||||||
|
return when (channel.channelType) {
|
||||||
|
ChannelType.DirectMessage -> {
|
||||||
|
val otherUser = channel.recipients?.find { it != RevoltAPI.selfId }
|
||||||
|
if (otherUser != null) {
|
||||||
|
val user = RevoltAPI.userCache[otherUser]
|
||||||
|
if (user != null && user.username?.contains(query) == true) {
|
||||||
|
listOf(
|
||||||
|
AutocompleteSuggestion.User(
|
||||||
|
user,
|
||||||
|
null,
|
||||||
|
query
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ChannelType.Group -> {
|
||||||
|
val users =
|
||||||
|
channel.recipients?.mapNotNull { RevoltAPI.userCache[it] } ?: emptyList()
|
||||||
|
users
|
||||||
|
.filter { it.username?.contains(query) ?: false }
|
||||||
|
.map {
|
||||||
|
AutocompleteSuggestion.User(
|
||||||
|
it,
|
||||||
|
null,
|
||||||
|
query
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ChannelType.SavedMessages -> {
|
||||||
|
val user = RevoltAPI.userCache[RevoltAPI.selfId]
|
||||||
|
return if (user != null && user.username?.contains(query) == true) {
|
||||||
|
listOf(
|
||||||
|
AutocompleteSuggestion.User(
|
||||||
|
user,
|
||||||
|
null,
|
||||||
|
query
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ChannelType.TextChannel, ChannelType.VoiceChannel -> {
|
||||||
|
if (serverId == null) return emptyList()
|
||||||
|
if (query.length < 2) return emptyList()
|
||||||
|
|
||||||
|
val byNickname = RevoltAPI.members.filterNamesFor(serverId, query)
|
||||||
|
.map { m -> m to RevoltAPI.userCache[m.id?.user] }.filter { (_, u) ->
|
||||||
|
u != null
|
||||||
|
}.map { (m, u) ->
|
||||||
|
m to u!!
|
||||||
|
}
|
||||||
|
val byUsername = RevoltAPI.userCache.values.filter {
|
||||||
|
it.username?.contains(
|
||||||
|
query,
|
||||||
|
ignoreCase = true
|
||||||
|
) ?: false
|
||||||
|
}.mapNotNull {
|
||||||
|
it.id?.let { _ ->
|
||||||
|
RevoltAPI.members.getMember(
|
||||||
|
serverId,
|
||||||
|
it.id
|
||||||
|
) to it
|
||||||
|
}
|
||||||
|
}.filter { (member, _) ->
|
||||||
|
member != null
|
||||||
|
}.map { (member, user) ->
|
||||||
|
member!! to user
|
||||||
|
}
|
||||||
|
|
||||||
|
val all = (byNickname + byUsername).distinctBy { it.first.id }
|
||||||
|
|
||||||
|
all.map {
|
||||||
|
AutocompleteSuggestion.User(
|
||||||
|
it.second,
|
||||||
|
it.first,
|
||||||
|
query
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
null -> emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -519,6 +519,8 @@ fun ChannelScreen(
|
||||||
),
|
),
|
||||||
forceSendButton = viewModel.pendingAttachments.isNotEmpty(),
|
forceSendButton = viewModel.pendingAttachments.isNotEmpty(),
|
||||||
disabled = viewModel.pendingAttachments.isNotEmpty() && viewModel.isSendingMessage,
|
disabled = viewModel.pendingAttachments.isNotEmpty() && viewModel.isSendingMessage,
|
||||||
|
channelId = channelId,
|
||||||
|
serverId = channel?.server,
|
||||||
editMode = viewModel.editingMessage != null,
|
editMode = viewModel.editingMessage != null,
|
||||||
cancelEdit = viewModel::cancelEditingMessage,
|
cancelEdit = viewModel::cancelEditingMessage,
|
||||||
onFocusChange = { nowFocused ->
|
onFocusChange = { nowFocused ->
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue