feat: add status functionality to status sheet

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2023-10-22 20:26:31 +02:00
parent 717e9f7890
commit f942218b6a
5 changed files with 178 additions and 42 deletions

View File

@ -5,10 +5,18 @@ import chat.revolt.api.RevoltError
import chat.revolt.api.RevoltHttp
import chat.revolt.api.RevoltJson
import chat.revolt.api.schemas.Profile
import chat.revolt.api.schemas.Status
import chat.revolt.api.schemas.User
import io.ktor.client.request.get
import io.ktor.client.request.patch
import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText
import io.ktor.http.ContentType
import io.ktor.http.contentType
import kotlinx.serialization.SerializationException
import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.json.JsonElement
suspend fun fetchSelf(): User {
val response = RevoltHttp.get("/users/@me")
@ -33,6 +41,41 @@ suspend fun fetchSelf(): User {
return user
}
suspend fun patchSelf(
status: Status? = null,
pure: Boolean = false,
) {
val body = mutableMapOf<String, JsonElement>()
if (status != null) {
body["status"] = RevoltJson.encodeToJsonElement(Status.serializer(), status)
}
val response = RevoltHttp.patch("/users/@me") {
contentType(ContentType.Application.Json)
setBody(
RevoltJson.encodeToString(
MapSerializer(
String.serializer(),
JsonElement.serializer()
), body
)
)
}
.bodyAsText()
if (RevoltAPI.selfId == null) {
throw Error("Self ID is null")
}
val currentUser = RevoltAPI.userCache[RevoltAPI.selfId] ?: fetchSelf()
val newUserKeys = RevoltJson.decodeFromString(User.serializer(), response)
val mergedUser = currentUser.mergeWithPartial(newUserKeys)
if (!pure) {
RevoltAPI.userCache[RevoltAPI.selfId!!] = mergedUser
}
}
suspend fun fetchUser(id: String): User {
val res = RevoltHttp.get("/users/$id")
@ -81,4 +124,4 @@ suspend fun fetchUserProfile(id: String): Profile {
}
return RevoltJson.decodeFromString(Profile.serializer(), response)
}
}

View File

@ -45,13 +45,23 @@ fun presenceFromStatus(status: String?, online: Boolean = true): Presence {
}
}
fun Presence.asApiName(): String {
return when (this) {
Presence.Online -> "Online"
Presence.Idle -> "Idle"
Presence.Dnd -> "Busy"
Presence.Focus -> "Focus"
Presence.Offline -> "Invisible"
}
}
fun presenceColour(presence: Presence): Color {
return when (presence) {
Presence.Online -> Color(0xFF00C853)
Presence.Idle -> Color(0xFFFFD600)
Presence.Dnd -> Color(0xFFD50000)
Presence.Focus -> Color(0xFF0091EA)
Presence.Offline -> Color(0xff546e7a)
Presence.Online -> Color(0xFF3ABF7E)
Presence.Idle -> Color(0xFFF39F00)
Presence.Dnd -> Color(0xFFF84848)
Presence.Focus -> Color(0xFF4799F0)
Presence.Offline -> Color(0xFFA5A5A5)
}
}

View File

@ -0,0 +1,99 @@
package chat.revolt.components.settings.profile
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import chat.revolt.R
import chat.revolt.components.generic.Presence
import chat.revolt.components.generic.presenceColour
fun Presence.stringResource(): Int {
return when (this) {
Presence.Online -> R.string.status_online
Presence.Idle -> R.string.status_idle
Presence.Dnd -> R.string.status_dnd
Presence.Focus -> R.string.status_focus
Presence.Offline -> R.string.status_invisible
}
}
@Composable
fun StatusPicker(
currentStatus: Presence,
onStatusChange: (Presence) -> Unit,
modifier: Modifier = Modifier
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier
) {
Text(
text = stringResource(R.string.status),
style = MaterialTheme.typography.labelLarge
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.clip(MaterialTheme.shapes.small)
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp))
) {
Presence.entries.forEach {
StatusButton(
presence = it,
selected = it == currentStatus,
onClick = onStatusChange,
modifier = modifier
)
}
}
Text(
text = stringResource(currentStatus.stringResource()),
style = MaterialTheme.typography.bodyLarge
)
}
}
@Composable
fun StatusButton(
presence: Presence,
selected: Boolean,
onClick: (Presence) -> Unit,
modifier: Modifier = Modifier
) {
Box(
modifier = modifier
.size(48.dp)
.clickable { onClick(presence) }
.then(
if (selected) Modifier.background(
MaterialTheme.colorScheme.surfaceColorAtElevation(6.dp)
) else Modifier
),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.clip(CircleShape)
.size(24.dp)
.background(presenceColour(presence))
)
}
}

View File

@ -1,11 +1,9 @@
package chat.revolt.sheets
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
@ -13,60 +11,45 @@ import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import chat.revolt.R
import chat.revolt.api.RevoltAPI
import chat.revolt.api.internals.ULID
import chat.revolt.api.routes.user.patchSelf
import chat.revolt.components.generic.SheetClickable
import chat.revolt.components.generic.UserAvatar
import chat.revolt.components.generic.asApiName
import chat.revolt.components.generic.presenceFromStatus
import chat.revolt.components.screens.settings.UserOverview
import chat.revolt.components.settings.profile.StatusPicker
import kotlinx.coroutines.launch
@Composable
fun StatusSheet(onBeforeNavigation: () -> Unit, onGoSettings: () -> Unit) {
val selfUser = RevoltAPI.userCache[RevoltAPI.selfId]!!
val scope = rememberCoroutineScope()
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(horizontal = 16.dp, vertical = 8.dp)
.verticalScroll(rememberScrollState())
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
UserAvatar(
username = selfUser.displayName ?: stringResource(id = R.string.unknown),
userId = selfUser.id ?: ULID.makeSpecial(0),
avatar = selfUser.avatar,
size = 48.dp,
presence = presenceFromStatus(
selfUser.status?.presence,
selfUser.online ?: false
)
)
UserOverview(selfUser)
Spacer(modifier = Modifier.width(12.dp))
Spacer(modifier = Modifier.height(16.dp))
Text(
text = AnnotatedString.Builder().apply {
if (selfUser.displayName != null) {
pushStyle(SpanStyle(fontWeight = FontWeight.Bold))
append(selfUser.displayName)
pop()
append("\n")
}
append("${selfUser.username}")
pushStyle(SpanStyle(fontWeight = FontWeight.ExtraLight))
append("#${selfUser.discriminator}")
pop()
}.toAnnotatedString()
)
}
StatusPicker(
currentStatus = presenceFromStatus(selfUser.status?.presence, selfUser.online ?: false),
onStatusChange = {
onBeforeNavigation()
scope.launch {
patchSelf(status = selfUser.status?.copy(presence = it.asApiName()))
}
}
)
Spacer(modifier = Modifier.height(8.dp))

View File

@ -181,6 +181,7 @@
<string name="reconnecting">Reconnecting…</string>
<string name="reconnected">Reconnected</string>
<string name="status">Status</string>
<string name="status_online">Online</string>
<string name="status_idle">Idle</string>
<string name="status_focus">Focus</string>