feat: basic member fetching in servers, roles in sheet
Signed-off-by: Infi <wingit@geist.ga>
This commit is contained in:
parent
6e179d3b49
commit
4f0845bf46
|
|
@ -92,7 +92,6 @@ val mainHandler = Handler(Looper.getMainLooper())
|
||||||
object RevoltAPI {
|
object RevoltAPI {
|
||||||
const val TOKEN_HEADER_NAME = "x-session-token"
|
const val TOKEN_HEADER_NAME = "x-session-token"
|
||||||
|
|
||||||
// FIXME discount caching solutions! LRU would be better but this is fine for now
|
|
||||||
val userCache = mutableStateMapOf<String, User>()
|
val userCache = mutableStateMapOf<String, User>()
|
||||||
val serverCache = mutableStateMapOf<String, Server>()
|
val serverCache = mutableStateMapOf<String, Server>()
|
||||||
val channelCache = mutableStateMapOf<String, ChannelSchema>()
|
val channelCache = mutableStateMapOf<String, ChannelSchema>()
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,23 @@ package chat.revolt.api.internals
|
||||||
|
|
||||||
import chat.revolt.api.schemas.Member
|
import chat.revolt.api.schemas.Member
|
||||||
|
|
||||||
@RequiresOptIn("Dummy API, does nothing or returns null.")
|
|
||||||
@Retention(AnnotationRetention.BINARY)
|
|
||||||
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
|
|
||||||
annotation class RvxDummyMemberAPI
|
|
||||||
|
|
||||||
object Members {
|
object Members {
|
||||||
@RvxDummyMemberAPI
|
// memberCache (mapping of serverId to userId to member)
|
||||||
|
private val memberCache = mutableMapOf<String, MutableMap<String, Member>>()
|
||||||
|
|
||||||
fun getMember(serverId: String, userId: String): Member? {
|
fun getMember(serverId: String, userId: String): Member? {
|
||||||
return null
|
return memberCache[serverId]?.get(userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasMember(serverId: String, userId: String): Boolean {
|
||||||
|
return memberCache[serverId]?.containsKey(userId) ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addMember(serverId: String, member: Member) {
|
||||||
|
if (!memberCache.containsKey(serverId)) {
|
||||||
|
memberCache[serverId] = mutableMapOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
memberCache[serverId]?.set(member.id.user, member)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -23,7 +23,7 @@ import kotlinx.serialization.builtins.ListSerializer
|
||||||
suspend fun fetchMessagesFromChannel(
|
suspend fun fetchMessagesFromChannel(
|
||||||
channelId: String,
|
channelId: String,
|
||||||
limit: Int = 50,
|
limit: Int = 50,
|
||||||
include_users: Boolean = false,
|
includeUsers: Boolean = false,
|
||||||
before: String? = null,
|
before: String? = null,
|
||||||
after: String? = null,
|
after: String? = null,
|
||||||
nearby: String? = null,
|
nearby: String? = null,
|
||||||
|
|
@ -33,7 +33,7 @@ suspend fun fetchMessagesFromChannel(
|
||||||
headers.append(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken)
|
headers.append(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken)
|
||||||
|
|
||||||
parameter("limit", limit)
|
parameter("limit", limit)
|
||||||
parameter("include_users", include_users)
|
parameter("include_users", includeUsers)
|
||||||
|
|
||||||
if (before != null) parameter("before", before)
|
if (before != null) parameter("before", before)
|
||||||
if (after != null) parameter("after", after)
|
if (after != null) parameter("after", after)
|
||||||
|
|
@ -42,7 +42,7 @@ suspend fun fetchMessagesFromChannel(
|
||||||
}
|
}
|
||||||
.bodyAsText()
|
.bodyAsText()
|
||||||
|
|
||||||
if (include_users) {
|
if (includeUsers) {
|
||||||
return RevoltJson.decodeFromString(
|
return RevoltJson.decodeFromString(
|
||||||
MessagesInChannel.serializer(),
|
MessagesInChannel.serializer(),
|
||||||
response
|
response
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,83 @@
|
||||||
package chat.revolt.api.routes.server
|
package chat.revolt.api.routes.server
|
||||||
|
|
||||||
import chat.revolt.api.RevoltAPI
|
import chat.revolt.api.RevoltAPI
|
||||||
|
import chat.revolt.api.RevoltError
|
||||||
import chat.revolt.api.RevoltHttp
|
import chat.revolt.api.RevoltHttp
|
||||||
import io.ktor.client.request.*
|
import chat.revolt.api.RevoltJson
|
||||||
|
import chat.revolt.api.internals.Members
|
||||||
|
import chat.revolt.api.schemas.Member
|
||||||
|
import chat.revolt.api.schemas.User
|
||||||
|
import io.ktor.client.request.get
|
||||||
|
import io.ktor.client.request.parameter
|
||||||
|
import io.ktor.client.request.put
|
||||||
|
import io.ktor.client.statement.bodyAsText
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.SerializationException
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class FetchMembersResponse(
|
||||||
|
val members: List<Member>,
|
||||||
|
val users: List<User>
|
||||||
|
)
|
||||||
|
|
||||||
suspend fun ackServer(serverId: String) {
|
suspend fun ackServer(serverId: String) {
|
||||||
RevoltHttp.put("/servers/$serverId/ack") {
|
RevoltHttp.put("/servers/$serverId/ack") {
|
||||||
headers.append(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken)
|
headers.append(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun fetchMembers(
|
||||||
|
serverId: String,
|
||||||
|
includeOffline: Boolean = false,
|
||||||
|
pure: Boolean = false
|
||||||
|
): FetchMembersResponse {
|
||||||
|
val response = RevoltHttp.get("/servers/$serverId/members") {
|
||||||
|
headers.append(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken)
|
||||||
|
|
||||||
|
parameter("exclude_offline", !includeOffline)
|
||||||
|
}
|
||||||
|
|
||||||
|
val responseContent = response.bodyAsText()
|
||||||
|
|
||||||
|
try {
|
||||||
|
val error = RevoltJson.decodeFromString(RevoltError.serializer(), responseContent)
|
||||||
|
throw Error(error.type)
|
||||||
|
} catch (e: SerializationException) {
|
||||||
|
// Not an error
|
||||||
|
}
|
||||||
|
|
||||||
|
val membersResponse =
|
||||||
|
RevoltJson.decodeFromString(FetchMembersResponse.serializer(), responseContent)
|
||||||
|
|
||||||
|
if (pure) {
|
||||||
|
return membersResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
membersResponse.members.forEach { member ->
|
||||||
|
if (!Members.hasMember(serverId, member.id.user)) {
|
||||||
|
Members.addMember(serverId, member)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
membersResponse.users.forEach { user ->
|
||||||
|
user.id?.let { RevoltAPI.userCache.putIfAbsent(it, user) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return membersResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun fetchMember(serverId: String, userId: String, pure: Boolean = false): Member {
|
||||||
|
val response = RevoltHttp.get("/servers/$serverId/members/$userId") {
|
||||||
|
headers.append(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
val member = RevoltJson.decodeFromString(Member.serializer(), response.bodyAsText())
|
||||||
|
|
||||||
|
if (!pure) {
|
||||||
|
if (!Members.hasMember(serverId, member.id.user)) {
|
||||||
|
Members.addMember(serverId, member)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return member
|
||||||
|
}
|
||||||
|
|
@ -54,6 +54,7 @@ import chat.revolt.R
|
||||||
import chat.revolt.api.RevoltAPI
|
import chat.revolt.api.RevoltAPI
|
||||||
import chat.revolt.api.realtime.DisconnectionState
|
import chat.revolt.api.realtime.DisconnectionState
|
||||||
import chat.revolt.api.realtime.RealtimeSocket
|
import chat.revolt.api.realtime.RealtimeSocket
|
||||||
|
import chat.revolt.api.routes.server.fetchMembers
|
||||||
import chat.revolt.api.schemas.User
|
import chat.revolt.api.schemas.User
|
||||||
import chat.revolt.api.settings.SyncedSettings
|
import chat.revolt.api.settings.SyncedSettings
|
||||||
import chat.revolt.components.chat.DisconnectedNotice
|
import chat.revolt.components.chat.DisconnectedNotice
|
||||||
|
|
@ -101,12 +102,12 @@ class ChatRouterViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setCurrentServer(serverId: String, save: Boolean = true) {
|
private suspend fun setCurrentServer(serverId: String, save: Boolean = true) {
|
||||||
currentServer = serverId
|
currentServer = serverId
|
||||||
|
|
||||||
if (save) viewModelScope.launch {
|
if (save) kvStorage.set("currentServer", serverId)
|
||||||
kvStorage.set("currentServer", serverId)
|
|
||||||
}
|
if (serverId != "home") fetchMembers(serverId, includeOffline = false, pure = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setSaveCurrentChannel(channelId: String) {
|
private fun setSaveCurrentChannel(channelId: String) {
|
||||||
|
|
@ -129,13 +130,17 @@ class ChatRouterViewModel @Inject constructor(
|
||||||
popUpTo(route)
|
popUpTo(route)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setCurrentServer("home")
|
viewModelScope.launch {
|
||||||
|
setCurrentServer("home")
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val channelId = RevoltAPI.serverCache[serverId]?.channels?.firstOrNull()
|
val channelId = RevoltAPI.serverCache[serverId]?.channels?.firstOrNull()
|
||||||
|
|
||||||
setCurrentServer(serverId, channelId != null)
|
viewModelScope.launch {
|
||||||
|
setCurrentServer(serverId, channelId != null)
|
||||||
|
}
|
||||||
|
|
||||||
if (channelId != null) {
|
if (channelId != null) {
|
||||||
navigateToChannel(channelId, navController)
|
navigateToChannel(channelId, navController)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import chat.revolt.api.RevoltAPI
|
import chat.revolt.api.RevoltAPI
|
||||||
import chat.revolt.api.RevoltJson
|
import chat.revolt.api.RevoltJson
|
||||||
|
import chat.revolt.api.internals.Members
|
||||||
import chat.revolt.api.internals.ULID
|
import chat.revolt.api.internals.ULID
|
||||||
import chat.revolt.api.realtime.RealtimeSocketFrames
|
import chat.revolt.api.realtime.RealtimeSocketFrames
|
||||||
import chat.revolt.api.realtime.frames.receivable.ChannelStartTypingFrame
|
import chat.revolt.api.realtime.frames.receivable.ChannelStartTypingFrame
|
||||||
|
|
@ -27,6 +28,7 @@ import chat.revolt.api.routes.channel.sendMessage
|
||||||
import chat.revolt.api.routes.microservices.autumn.FileArgs
|
import chat.revolt.api.routes.microservices.autumn.FileArgs
|
||||||
import chat.revolt.api.routes.microservices.autumn.MAX_ATTACHMENTS_PER_MESSAGE
|
import chat.revolt.api.routes.microservices.autumn.MAX_ATTACHMENTS_PER_MESSAGE
|
||||||
import chat.revolt.api.routes.microservices.autumn.uploadToAutumn
|
import chat.revolt.api.routes.microservices.autumn.uploadToAutumn
|
||||||
|
import chat.revolt.api.routes.server.fetchMember
|
||||||
import chat.revolt.api.routes.user.addUserIfUnknown
|
import chat.revolt.api.routes.user.addUserIfUnknown
|
||||||
import chat.revolt.api.schemas.Channel
|
import chat.revolt.api.schemas.Channel
|
||||||
import chat.revolt.api.schemas.Message
|
import chat.revolt.api.schemas.Message
|
||||||
|
|
@ -101,7 +103,7 @@ class ChannelScreenViewModel : ViewModel() {
|
||||||
fetchMessagesFromChannel(
|
fetchMessagesFromChannel(
|
||||||
activeChannel!!.id!!,
|
activeChannel!!.id!!,
|
||||||
limit = 50,
|
limit = 50,
|
||||||
true,
|
includeUsers = true,
|
||||||
before = if (renderableMessages.isNotEmpty()) {
|
before = if (renderableMessages.isNotEmpty()) {
|
||||||
renderableMessages.last().id
|
renderableMessages.last().id
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -119,6 +121,18 @@ class ChannelScreenViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
messages.add(message)
|
messages.add(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it.users?.forEach { user ->
|
||||||
|
if (!RevoltAPI.userCache.containsKey(user.id)) {
|
||||||
|
RevoltAPI.userCache[user.id!!] = user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it.members?.forEach { member ->
|
||||||
|
if (!Members.hasMember(member.id.server, member.id.user)) {
|
||||||
|
Members.addMember(member.id.server, member)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
regroupMessages(renderableMessages + messages)
|
regroupMessages(renderableMessages + messages)
|
||||||
|
|
@ -260,6 +274,7 @@ class ChannelScreenViewModel : ViewModel() {
|
||||||
if (it.channel != activeChannel?.id) return@onEach
|
if (it.channel != activeChannel?.id) return@onEach
|
||||||
|
|
||||||
addUserIfUnknown(it.author!!)
|
addUserIfUnknown(it.author!!)
|
||||||
|
activeChannel?.server?.let { s -> fetchMember(s, it.author) }
|
||||||
regroupMessages(listOf(it) + renderableMessages)
|
regroupMessages(listOf(it) + renderableMessages)
|
||||||
ackNewest()
|
ackNewest()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,19 @@
|
||||||
package chat.revolt.sheets
|
package chat.revolt.sheets
|
||||||
|
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -17,17 +24,20 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import chat.revolt.R
|
import chat.revolt.R
|
||||||
import chat.revolt.api.RevoltAPI
|
import chat.revolt.api.RevoltAPI
|
||||||
import chat.revolt.api.internals.Members
|
import chat.revolt.api.internals.Members
|
||||||
import chat.revolt.api.internals.RvxDummyMemberAPI
|
import chat.revolt.api.internals.WebCompat
|
||||||
|
import chat.revolt.api.internals.solidColor
|
||||||
import chat.revolt.api.routes.user.fetchUserProfile
|
import chat.revolt.api.routes.user.fetchUserProfile
|
||||||
import chat.revolt.api.schemas.Profile
|
import chat.revolt.api.schemas.Profile
|
||||||
import chat.revolt.components.generic.UIMarkdown
|
import chat.revolt.components.generic.UIMarkdown
|
||||||
import chat.revolt.components.screens.settings.RawUserOverview
|
import chat.revolt.components.screens.settings.RawUserOverview
|
||||||
|
|
||||||
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun UserContextSheet(
|
fun UserContextSheet(
|
||||||
userId: String,
|
userId: String,
|
||||||
|
|
@ -36,7 +46,6 @@ fun UserContextSheet(
|
||||||
) {
|
) {
|
||||||
val user = RevoltAPI.userCache[userId]
|
val user = RevoltAPI.userCache[userId]
|
||||||
|
|
||||||
@OptIn(RvxDummyMemberAPI::class)
|
|
||||||
val member = serverId?.let { Members.getMember(it, userId) }
|
val member = serverId?.let { Members.getMember(it, userId) }
|
||||||
|
|
||||||
val server = RevoltAPI.serverCache[serverId]
|
val server = RevoltAPI.serverCache[serverId]
|
||||||
|
|
@ -66,9 +75,44 @@ fun UserContextSheet(
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp)
|
modifier = Modifier.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
member?.roles?.let {
|
||||||
text = "sheet for ${server?.name ?: "serverless (omg jamstack reference??)"}",
|
Text(
|
||||||
)
|
text = stringResource(id = R.string.user_context_sheet_category_roles),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
modifier = Modifier.padding(bottom = 10.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
FlowRow(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
it.forEach { roleId ->
|
||||||
|
val role = server?.roles?.get(roleId)
|
||||||
|
role?.let {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.border(
|
||||||
|
border = BorderStroke(
|
||||||
|
width = 1.dp,
|
||||||
|
brush = role.colour?.let { WebCompat.parseColour(it) }
|
||||||
|
?: Brush.solidColor(LocalContentColor.current),
|
||||||
|
),
|
||||||
|
shape = MaterialTheme.shapes.small
|
||||||
|
)
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = role.name ?: roleId,
|
||||||
|
style = LocalTextStyle.current.copy(
|
||||||
|
brush = role.colour?.let { WebCompat.parseColour(it) }
|
||||||
|
?: Brush.solidColor(LocalContentColor.current)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.user_context_sheet_category_bio),
|
text = stringResource(id = R.string.user_context_sheet_category_bio),
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,7 @@
|
||||||
|
|
||||||
<string name="user_context_sheet_category_bio">Bio</string>
|
<string name="user_context_sheet_category_bio">Bio</string>
|
||||||
<string name="user_context_sheet_bio_empty">This user hasn\'t set a bio yet.</string>
|
<string name="user_context_sheet_bio_empty">This user hasn\'t set a bio yet.</string>
|
||||||
|
<string name="user_context_sheet_category_roles">Roles</string>
|
||||||
|
|
||||||
<string name="add_server_sheet_title">Add a server</string>
|
<string name="add_server_sheet_title">Add a server</string>
|
||||||
<string name="add_server_sheet_join_by_invite">Join by invite code or link</string>
|
<string name="add_server_sheet_join_by_invite">Join by invite code or link</string>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue