feat: mention autocomplete

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2023-12-07 17:04:24 +01:00
parent 924568a7b3
commit 6ac347ef07
4 changed files with 136 additions and 10 deletions

View File

@ -38,4 +38,10 @@ class Members {
member.nickname?.let { userId to member.nickname }
}?.toMap() ?: emptyMap()
}
fun filterNamesFor(serverId: String, query: String): List<Member> {
return memberCache[serverId]?.values?.filter { member ->
member.nickname?.contains(query, ignoreCase = true) ?: false
} ?: emptyList()
}
}

View File

@ -79,6 +79,8 @@ import chat.revolt.api.REVOLT_FILES
import chat.revolt.api.schemas.ChannelType
import chat.revolt.api.schemas.Member
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 kotlinx.coroutines.launch
@ -155,9 +157,9 @@ fun NativeMessageField(
modifier: Modifier = Modifier,
forceSendButton: Boolean = false,
disabled: Boolean = false,
editMode: Boolean = false,
serverId: String? = null,
channelId: String? = null,
editMode: Boolean = false,
cancelEdit: () -> Unit = {},
onFocusChange: (Boolean) -> Unit = {},
onSelectionChange: (Pair<Int, Int>) -> Unit = {}
@ -247,10 +249,15 @@ fun NativeMessageField(
},
label = { Text("@${item.user.username}#${item.user.discriminator}") },
icon = {
Icon(
painter = painterResource(R.drawable.ic_human_greeting_variant_24dp),
contentDescription = null,
modifier = Modifier.size(SuggestionChipDefaults.IconSize)
UserAvatar(
username = item.user.username
?: stringResource(R.string.unknown),
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
@ -270,11 +277,7 @@ fun NativeMessageField(
},
label = { Text("#${item.channel.name}") },
icon = {
Icon(
painter = painterResource(R.drawable.ic_pound_24dp),
contentDescription = null,
modifier = Modifier.size(SuggestionChipDefaults.IconSize)
)
item.channel.channelType?.let { type -> ChannelIcon(channelType = type) }
},
modifier = Modifier
.animateItemPlacement()
@ -360,6 +363,8 @@ fun NativeMessageField(
AndroidView(
factory = { context ->
object : androidx.appcompat.widget.AppCompatEditText(context) {
var serverId: String? = null
override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection? {
var ic = super.onCreateInputConnection(outAttrs)
EditorInfoCompat.setContentMimeTypes(
@ -402,6 +407,17 @@ fun NativeMessageField(
Autocomplete.emoji(lastWord.substring(1))
)
}
lastWord.startsWith('@') -> {
if (channelId == null) return
autocompleteSuggestions.addAll(
Autocomplete.user(
channelId,
this.serverId,
lastWord.substring(1)
)
)
}
}
}
}.apply {
@ -489,6 +505,7 @@ fun NativeMessageField(
it.setSelection(value.length)
}
it.hint = it.context.getString(placeholderResource, channelName)
it.serverId = serverId
},
modifier = Modifier
.weight(1f)

View File

@ -1,6 +1,7 @@
package chat.revolt.internals
import chat.revolt.api.RevoltAPI
import chat.revolt.api.schemas.ChannelType
import chat.revolt.components.chat.AutocompleteSuggestion
object Autocomplete {
@ -33,4 +34,104 @@ object Autocomplete {
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()
}
}
}

View File

@ -519,6 +519,8 @@ fun ChannelScreen(
),
forceSendButton = viewModel.pendingAttachments.isNotEmpty(),
disabled = viewModel.pendingAttachments.isNotEmpty() && viewModel.isSendingMessage,
channelId = channelId,
serverId = channel?.server,
editMode = viewModel.editingMessage != null,
cancelEdit = viewModel::cancelEditingMessage,
onFocusChange = { nowFocused ->