feat: new add server sheet with server creation

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2025-04-25 20:55:13 +02:00
parent 67816d9cfe
commit 48e63e549a
9 changed files with 1129 additions and 137 deletions

View File

@ -6,11 +6,14 @@ import chat.revolt.api.RevoltHttp
import chat.revolt.api.RevoltJson import chat.revolt.api.RevoltJson
import chat.revolt.api.api import chat.revolt.api.api
import chat.revolt.api.schemas.Member import chat.revolt.api.schemas.Member
import chat.revolt.api.schemas.ServerWithChannelObjects
import chat.revolt.api.schemas.User import chat.revolt.api.schemas.User
import io.ktor.client.request.delete import io.ktor.client.request.delete
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.request.parameter import io.ktor.client.request.parameter
import io.ktor.client.request.post
import io.ktor.client.request.put import io.ktor.client.request.put
import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationException
@ -91,3 +94,31 @@ suspend fun leaveOrDeleteServer(serverId: String, leaveSilently: Boolean = false
parameter("leave_silently", leaveSilently) parameter("leave_silently", leaveSilently)
} }
} }
@Serializable
data class ServerCreationBody(
val name: String,
val description: String? = null,
val nsfw: Boolean = false
)
suspend fun createServer(
name: String,
description: String = "",
nsfw: Boolean = false
): ServerWithChannelObjects {
val body = ServerCreationBody(name, description, nsfw)
val response = RevoltHttp.post("/servers/create".api()) {
setBody(RevoltJson.encodeToString(ServerCreationBody.serializer(), body))
}
try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response.bodyAsText())
throw Exception(error.type)
} catch (e: SerializationException) {
// Not an error
}
return RevoltJson.decodeFromString(ServerWithChannelObjects.serializer(), response.bodyAsText())
}

View File

@ -100,3 +100,27 @@ data class EmojiParent(
val type: String? = null, val type: String? = null,
val id: String? = null val id: String? = null
) )
/**
* Like [Server] but with complete channel objects instead of only IDs.
*/
@Serializable
data class ServerWithChannelObjects(
@SerialName("_id")
val id: String? = null,
val owner: String? = null,
val name: String? = null,
val description: String? = null,
val channels: List<Channel>? = null,
val categories: List<Category>? = null,
@SerialName("system_messages")
val systemMessages: SystemMessages? = null,
val roles: Map<String, Role>? = null,
@SerialName("default_permissions")
val defaultPermissions: Long? = null,
val icon: AutumnResource? = null,
val banner: AutumnResource? = null,
val flags: Long? = null,
val analytics: Boolean? = null,
val discoverable: Boolean? = null
)

View File

@ -0,0 +1,61 @@
package chat.revolt.composables.sheets
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProvideTextStyle
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.unit.dp
/**
* Sheet selection. Used when a modal sheet prompts a choice out of x options.
* The choices have an extended icon, a title and a description.
* An end-facing chevron is shown on the end side of the row.
*/
@Composable
fun SheetSelection(
icon: @Composable () -> Unit,
title: @Composable () -> Unit,
description: @Composable () -> Unit,
modifier: Modifier = Modifier,
onClick: () -> Unit,
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.clip(MaterialTheme.shapes.medium)
.clickable(onClick = onClick)
.padding(16.dp)
) {
icon()
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier
.weight(1f)
.padding(start = 8.dp)
.then(modifier)
) {
ProvideTextStyle(MaterialTheme.typography.titleMedium) {
title()
}
ProvideTextStyle(MaterialTheme.typography.bodyMedium) {
description()
}
}
Icon(
imageVector = Icons.AutoMirrored.Default.KeyboardArrowRight,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}

View File

@ -0,0 +1,79 @@
package chat.revolt.composables.vectorassets
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
val CreateServer: ImageVector
@Composable
get() {
if (_CreateServer != null) {
return _CreateServer!!
}
_CreateServer = ImageVector.Builder(
name = "CreateServer",
defaultWidth = 605.dp,
defaultHeight = 604.dp,
viewportWidth = 605f,
viewportHeight = 604f
).apply {
path(fill = SolidColor(MaterialTheme.colorScheme.primaryContainer)) {
moveTo(270.33f, 36.02f)
curveTo(287.45f, 16.89f, 317.41f, 16.89f, 334.54f, 36.02f)
lineTo(374.1f, 80.21f)
curveTo(382.83f, 89.96f, 395.52f, 95.22f, 408.59f, 94.5f)
lineTo(467.8f, 91.22f)
curveTo(493.45f, 89.8f, 514.63f, 110.99f, 513.21f, 136.63f)
lineTo(509.94f, 195.85f)
curveTo(509.22f, 208.91f, 514.47f, 221.6f, 524.22f, 230.34f)
lineTo(568.41f, 269.89f)
curveTo(587.54f, 287.02f, 587.54f, 316.98f, 568.41f, 334.11f)
lineTo(524.22f, 373.67f)
curveTo(514.47f, 382.4f, 509.22f, 395.08f, 509.94f, 408.15f)
lineTo(513.21f, 467.37f)
curveTo(514.63f, 493.01f, 493.45f, 514.2f, 467.8f, 512.78f)
lineTo(408.59f, 509.5f)
curveTo(395.52f, 508.78f, 382.83f, 514.04f, 374.1f, 523.79f)
lineTo(334.54f, 567.97f)
curveTo(317.41f, 587.11f, 287.45f, 587.11f, 270.33f, 567.97f)
lineTo(230.77f, 523.79f)
curveTo(222.04f, 514.04f, 209.35f, 508.78f, 196.28f, 509.5f)
lineTo(137.07f, 512.78f)
curveTo(111.42f, 514.2f, 90.24f, 493.01f, 91.66f, 467.37f)
lineTo(94.93f, 408.15f)
curveTo(95.65f, 395.08f, 90.4f, 382.4f, 80.64f, 373.67f)
lineTo(36.46f, 334.11f)
curveTo(17.32f, 316.98f, 17.32f, 287.02f, 36.46f, 269.89f)
lineTo(80.64f, 230.34f)
curveTo(90.4f, 221.6f, 95.65f, 208.91f, 94.93f, 195.85f)
lineTo(91.66f, 136.63f)
curveTo(90.24f, 110.99f, 111.42f, 89.8f, 137.07f, 91.22f)
lineTo(196.28f, 94.5f)
curveTo(209.35f, 95.22f, 222.04f, 89.96f, 230.77f, 80.21f)
lineTo(270.33f, 36.02f)
close()
}
path(fill = SolidColor(MaterialTheme.colorScheme.onPrimaryContainer)) {
moveTo(285.02f, 404.6f)
verticalLineTo(199.4f)
horizontalLineTo(319.85f)
verticalLineTo(404.6f)
horizontalLineTo(285.02f)
close()
moveTo(199.83f, 319.41f)
verticalLineTo(284.59f)
horizontalLineTo(405.03f)
verticalLineTo(319.41f)
horizontalLineTo(199.83f)
close()
}
}.build()
return _CreateServer!!
}
@Suppress("ObjectPropertyName")
private var _CreateServer: ImageVector? = null

View File

@ -0,0 +1,54 @@
package chat.revolt.composables.vectorassets
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
val JoinServer: ImageVector
@Composable
get() {
if (_JoinServer != null) {
return _JoinServer!!
}
_JoinServer = ImageVector.Builder(
name = "JoinServer",
defaultWidth = 518.dp,
defaultHeight = 338.dp,
viewportWidth = 518f,
viewportHeight = 338f
).apply {
path(fill = SolidColor(MaterialTheme.colorScheme.secondaryContainer)) {
moveTo(196.04f, 208.6f)
curveTo(174.16f, 186.73f, 174.16f, 151.27f, 196.04f, 129.4f)
lineTo(308.76f, 16.68f)
curveTo(330.63f, -5.19f, 366.08f, -5.19f, 387.95f, 16.68f)
lineTo(500.68f, 129.4f)
curveTo(522.55f, 151.27f, 522.55f, 186.73f, 500.68f, 208.6f)
lineTo(387.95f, 321.32f)
curveTo(366.08f, 343.19f, 330.63f, 343.19f, 308.76f, 321.32f)
lineTo(196.04f, 208.6f)
close()
}
path(fill = SolidColor(MaterialTheme.colorScheme.onSurface)) {
moveTo(85.98f, 250.61f)
lineTo(67.19f, 231.99f)
lineTo(116.5f, 182.68f)
horizontalLineTo(0.96f)
verticalLineTo(155.32f)
horizontalLineTo(116.5f)
lineTo(67.19f, 106.09f)
lineTo(85.98f, 87.39f)
lineTo(167.59f, 169f)
lineTo(85.98f, 250.61f)
close()
}
}.build()
return _JoinServer!!
}
@Suppress("ObjectPropertyName")
private var _JoinServer: ImageVector? = null

View File

@ -0,0 +1,399 @@
package chat.revolt.composables.vectorassets
import androidx.compose.foundation.Image
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
val NewServer: ImageVector
@Composable
get() {
if (_NewServer != null) {
return _NewServer!!
}
_NewServer = ImageVector.Builder(
name = "NewServer",
defaultWidth = 570.dp,
defaultHeight = 508.dp,
viewportWidth = 570f,
viewportHeight = 508f
).apply {
path(fill = SolidColor(MaterialTheme.colorScheme.primaryContainer)) {
moveTo(131.17f, 166.37f)
lineTo(131.17f, 166.37f)
arcTo(
131.03f,
131.03f,
0f,
isMoreThanHalf = false,
isPositiveArc = true,
262.2f,
297.4f
)
lineTo(262.2f, 297.4f)
arcTo(
131.03f,
131.03f,
0f,
isMoreThanHalf = false,
isPositiveArc = true,
131.17f,
428.43f
)
lineTo(131.17f, 428.43f)
arcTo(
131.03f,
131.03f,
0f,
isMoreThanHalf = false,
isPositiveArc = true,
0.14f,
297.4f
)
lineTo(0.14f, 297.4f)
arcTo(
131.03f,
131.03f,
0f,
isMoreThanHalf = false,
isPositiveArc = true,
131.17f,
166.37f
)
close()
}
path(fill = SolidColor(MaterialTheme.colorScheme.onPrimaryContainer)) {
moveTo(87.36f, 357.23f)
lineTo(92.08f, 330.64f)
horizontalLineTo(65.48f)
lineTo(67.81f, 317.34f)
horizontalLineTo(94.4f)
lineTo(101.45f, 277.45f)
horizontalLineTo(74.86f)
lineTo(77.18f, 264.15f)
horizontalLineTo(103.78f)
lineTo(108.5f, 237.56f)
horizontalLineTo(121.79f)
lineTo(117.07f, 264.15f)
horizontalLineTo(156.96f)
lineTo(161.68f, 237.56f)
horizontalLineTo(174.98f)
lineTo(170.26f, 264.15f)
horizontalLineTo(196.85f)
lineTo(194.53f, 277.45f)
horizontalLineTo(167.93f)
lineTo(160.89f, 317.34f)
horizontalLineTo(187.48f)
lineTo(185.15f, 330.64f)
horizontalLineTo(158.56f)
lineTo(153.84f, 357.23f)
horizontalLineTo(140.54f)
lineTo(145.26f, 330.64f)
horizontalLineTo(105.37f)
lineTo(100.65f, 357.23f)
horizontalLineTo(87.36f)
close()
moveTo(114.75f, 277.45f)
lineTo(107.7f, 317.34f)
horizontalLineTo(147.59f)
lineTo(154.64f, 277.45f)
horizontalLineTo(114.75f)
close()
}
path(fill = SolidColor(MaterialTheme.colorScheme.secondaryContainer)) {
moveTo(369.52f, 314.28f)
lineTo(369.52f, 314.28f)
arcTo(
96.66f,
96.66f,
0f,
isMoreThanHalf = false,
isPositiveArc = true,
466.18f,
410.94f
)
lineTo(466.18f, 410.94f)
arcTo(
96.66f,
96.66f,
0f,
isMoreThanHalf = false,
isPositiveArc = true,
369.52f,
507.6f
)
lineTo(369.52f, 507.6f)
arcTo(
96.66f,
96.66f,
0f,
isMoreThanHalf = false,
isPositiveArc = true,
272.86f,
410.94f
)
lineTo(272.86f, 410.94f)
arcTo(
96.66f,
96.66f,
0f,
isMoreThanHalf = false,
isPositiveArc = true,
369.52f,
314.28f
)
close()
}
path(fill = SolidColor(MaterialTheme.colorScheme.onSecondaryContainer)) {
moveTo(389.14f, 441.79f)
verticalLineTo(451.6f)
horizontalLineTo(320.48f)
verticalLineTo(441.79f)
curveTo(320.48f, 441.79f, 320.48f, 422.18f, 354.81f, 422.18f)
curveTo(389.14f, 422.18f, 389.14f, 441.79f, 389.14f, 441.79f)
close()
moveTo(371.97f, 395.2f)
curveTo(371.97f, 391.81f, 370.97f, 388.49f, 369.08f, 385.67f)
curveTo(367.2f, 382.84f, 364.51f, 380.64f, 361.38f, 379.35f)
curveTo(358.24f, 378.05f, 354.79f, 377.71f, 351.46f, 378.37f)
curveTo(348.13f, 379.03f, 345.07f, 380.67f, 342.67f, 383.07f)
curveTo(340.27f, 385.47f, 338.64f, 388.52f, 337.97f, 391.86f)
curveTo(337.31f, 395.18f, 337.65f, 398.64f, 338.95f, 401.77f)
curveTo(340.25f, 404.91f, 342.45f, 407.59f, 345.27f, 409.48f)
curveTo(348.1f, 411.36f, 351.41f, 412.37f, 354.81f, 412.37f)
curveTo(359.36f, 412.37f, 363.73f, 410.56f, 366.95f, 407.34f)
curveTo(370.17f, 404.12f, 371.97f, 399.76f, 371.97f, 395.2f)
close()
moveTo(388.84f, 422.18f)
curveTo(391.86f, 424.51f, 394.33f, 427.48f, 396.07f, 430.86f)
curveTo(397.82f, 434.25f, 398.8f, 437.98f, 398.95f, 441.79f)
verticalLineTo(451.6f)
horizontalLineTo(418.57f)
verticalLineTo(441.79f)
curveTo(418.57f, 441.79f, 418.57f, 423.99f, 388.84f, 422.18f)
close()
moveTo(384.23f, 378.04f)
curveTo(380.86f, 378.02f, 377.56f, 379.03f, 374.77f, 380.93f)
curveTo(377.75f, 385.09f, 379.35f, 390.08f, 379.35f, 395.2f)
curveTo(379.35f, 400.32f, 377.75f, 405.31f, 374.77f, 409.48f)
curveTo(377.56f, 411.38f, 380.86f, 412.38f, 384.23f, 412.37f)
curveTo(388.79f, 412.37f, 393.15f, 410.56f, 396.37f, 407.34f)
curveTo(399.59f, 404.12f, 401.4f, 399.76f, 401.4f, 395.2f)
curveTo(401.4f, 390.65f, 399.59f, 386.29f, 396.37f, 383.07f)
curveTo(393.15f, 379.85f, 388.79f, 378.04f, 384.23f, 378.04f)
close()
}
path(fill = SolidColor(MaterialTheme.colorScheme.errorContainer)) {
moveTo(202.3f, 40.46f)
lineTo(202.3f, 40.46f)
arcTo(
59.91f,
59.91f,
0f,
isMoreThanHalf = false,
isPositiveArc = true,
262.2f,
100.36f
)
lineTo(262.2f, 100.36f)
arcTo(
59.91f,
59.91f,
0f,
isMoreThanHalf = false,
isPositiveArc = true,
202.3f,
160.27f
)
lineTo(202.3f, 160.27f)
arcTo(
59.91f,
59.91f,
0f,
isMoreThanHalf = false,
isPositiveArc = true,
142.39f,
100.36f
)
lineTo(142.39f, 100.36f)
arcTo(
59.91f,
59.91f,
0f,
isMoreThanHalf = false,
isPositiveArc = true,
202.3f,
40.46f
)
close()
}
path(fill = SolidColor(MaterialTheme.colorScheme.onErrorContainer)) {
moveTo(202.3f, 67.27f)
curveTo(203.91f, 67.27f, 205.46f, 67.91f, 206.6f, 69.05f)
curveTo(207.74f, 70.19f, 208.38f, 71.73f, 208.38f, 73.34f)
curveTo(208.38f, 75.59f, 207.16f, 77.57f, 205.34f, 78.6f)
verticalLineTo(82.46f)
horizontalLineTo(208.38f)
curveTo(214.02f, 82.46f, 219.43f, 84.71f, 223.42f, 88.7f)
curveTo(227.41f, 92.69f, 229.65f, 98.1f, 229.65f, 103.74f)
horizontalLineTo(232.69f)
curveTo(233.5f, 103.74f, 234.27f, 104.06f, 234.84f, 104.63f)
curveTo(235.41f, 105.2f, 235.73f, 105.97f, 235.73f, 106.78f)
verticalLineTo(115.9f)
curveTo(235.73f, 116.71f, 235.41f, 117.48f, 234.84f, 118.05f)
curveTo(234.27f, 118.62f, 233.5f, 118.94f, 232.69f, 118.94f)
horizontalLineTo(229.65f)
verticalLineTo(121.98f)
curveTo(229.65f, 123.59f, 229.01f, 125.14f, 227.87f, 126.28f)
curveTo(226.73f, 127.42f, 225.19f, 128.06f, 223.57f, 128.06f)
horizontalLineTo(181.02f)
curveTo(179.41f, 128.06f, 177.86f, 127.42f, 176.72f, 126.28f)
curveTo(175.58f, 125.14f, 174.94f, 123.59f, 174.94f, 121.98f)
verticalLineTo(118.94f)
horizontalLineTo(171.9f)
curveTo(171.1f, 118.94f, 170.32f, 118.62f, 169.75f, 118.05f)
curveTo(169.18f, 117.48f, 168.86f, 116.71f, 168.86f, 115.9f)
verticalLineTo(106.78f)
curveTo(168.86f, 105.97f, 169.18f, 105.2f, 169.75f, 104.63f)
curveTo(170.32f, 104.06f, 171.1f, 103.74f, 171.9f, 103.74f)
horizontalLineTo(174.94f)
curveTo(174.94f, 98.1f, 177.18f, 92.69f, 181.17f, 88.7f)
curveTo(185.16f, 84.71f, 190.57f, 82.46f, 196.22f, 82.46f)
horizontalLineTo(199.26f)
verticalLineTo(78.6f)
curveTo(197.43f, 77.57f, 196.22f, 75.59f, 196.22f, 73.34f)
curveTo(196.22f, 71.73f, 196.86f, 70.19f, 198f, 69.05f)
curveTo(199.14f, 67.91f, 200.68f, 67.27f, 202.3f, 67.27f)
close()
moveTo(188.62f, 100.7f)
curveTo(186.6f, 100.7f, 184.67f, 101.5f, 183.25f, 102.93f)
curveTo(181.82f, 104.35f, 181.02f, 106.29f, 181.02f, 108.3f)
curveTo(181.02f, 110.32f, 181.82f, 112.25f, 183.25f, 113.67f)
curveTo(184.67f, 115.1f, 186.6f, 115.9f, 188.62f, 115.9f)
curveTo(190.63f, 115.9f, 192.57f, 115.1f, 193.99f, 113.67f)
curveTo(195.42f, 112.25f, 196.22f, 110.32f, 196.22f, 108.3f)
curveTo(196.22f, 106.29f, 195.42f, 104.35f, 193.99f, 102.93f)
curveTo(192.57f, 101.5f, 190.63f, 100.7f, 188.62f, 100.7f)
close()
moveTo(215.98f, 100.7f)
curveTo(213.96f, 100.7f, 212.03f, 101.5f, 210.6f, 102.93f)
curveTo(209.18f, 104.35f, 208.38f, 106.29f, 208.38f, 108.3f)
curveTo(208.38f, 110.32f, 209.18f, 112.25f, 210.6f, 113.67f)
curveTo(212.03f, 115.1f, 213.96f, 115.9f, 215.98f, 115.9f)
curveTo(217.99f, 115.9f, 219.92f, 115.1f, 221.35f, 113.67f)
curveTo(222.77f, 112.25f, 223.57f, 110.32f, 223.57f, 108.3f)
curveTo(223.57f, 106.29f, 222.77f, 104.35f, 221.35f, 102.93f)
curveTo(219.92f, 101.5f, 217.99f, 100.7f, 215.98f, 100.7f)
close()
}
path(fill = SolidColor(MaterialTheme.colorScheme.tertiaryContainer)) {
moveTo(421.36f, 0.4f)
lineTo(421.36f, 0.4f)
arcTo(
148.5f,
148.5f,
0f,
isMoreThanHalf = false,
isPositiveArc = true,
569.86f,
148.9f
)
lineTo(569.86f, 148.9f)
arcTo(
148.5f,
148.5f,
0f,
isMoreThanHalf = false,
isPositiveArc = true,
421.36f,
297.4f
)
lineTo(421.36f, 297.4f)
arcTo(
148.5f,
148.5f,
0f,
isMoreThanHalf = false,
isPositiveArc = true,
272.86f,
148.9f
)
lineTo(272.86f, 148.9f)
arcTo(
148.5f,
148.5f,
0f,
isMoreThanHalf = false,
isPositiveArc = true,
421.36f,
0.4f
)
close()
}
path(fill = SolidColor(MaterialTheme.colorScheme.onTertiaryContainer)) {
moveTo(421.36f, 171.5f)
curveTo(427.47f, 171.5f, 432.67f, 169.24f, 437.26f, 164.8f)
curveTo(441.71f, 160.2f, 443.97f, 155f, 443.97f, 148.9f)
curveTo(443.97f, 142.8f, 441.71f, 137.6f, 437.26f, 133f)
curveTo(432.67f, 128.56f, 427.47f, 126.3f, 421.36f, 126.3f)
curveTo(415.26f, 126.3f, 410.06f, 128.56f, 405.47f, 133f)
curveTo(401.02f, 137.6f, 398.76f, 142.8f, 398.76f, 148.9f)
curveTo(398.76f, 155f, 401.02f, 160.2f, 405.47f, 164.8f)
curveTo(410.06f, 169.24f, 415.26f, 171.5f, 421.36f, 171.5f)
close()
moveTo(421.36f, 73.56f)
curveTo(442.08f, 73.56f, 459.79f, 81.09f, 474.48f, 95.78f)
curveTo(489.17f, 110.47f, 496.71f, 128.18f, 496.71f, 148.9f)
verticalLineTo(159.83f)
curveTo(496.71f, 167.36f, 494.07f, 173.76f, 489.17f, 179.04f)
curveTo(483.9f, 184.09f, 477.87f, 186.57f, 470.34f, 186.57f)
curveTo(461.3f, 186.57f, 453.84f, 182.81f, 448.19f, 175.27f)
curveTo(440.65f, 182.81f, 431.76f, 186.57f, 421.36f, 186.57f)
curveTo(411.04f, 186.57f, 402.15f, 182.81f, 394.69f, 175.57f)
curveTo(387.46f, 168.11f, 383.69f, 159.3f, 383.69f, 148.9f)
curveTo(383.69f, 138.58f, 387.46f, 129.69f, 394.69f, 122.23f)
curveTo(402.15f, 115f, 411.04f, 111.23f, 421.36f, 111.23f)
curveTo(431.76f, 111.23f, 440.58f, 115f, 448.04f, 122.23f)
curveTo(455.27f, 129.69f, 459.04f, 138.58f, 459.04f, 148.9f)
verticalLineTo(159.83f)
curveTo(459.04f, 162.91f, 460.24f, 165.63f, 462.5f, 167.96f)
curveTo(464.76f, 170.3f, 467.4f, 171.5f, 470.34f, 171.5f)
curveTo(473.5f, 171.5f, 476.14f, 170.3f, 478.4f, 167.96f)
curveTo(480.66f, 165.63f, 481.64f, 162.91f, 481.64f, 159.83f)
verticalLineTo(148.9f)
curveTo(481.64f, 132.4f, 475.84f, 118.24f, 463.93f, 106.33f)
curveTo(452.03f, 94.43f, 437.86f, 88.62f, 421.36f, 88.62f)
curveTo(404.86f, 88.62f, 390.7f, 94.43f, 378.8f, 106.33f)
curveTo(366.89f, 118.24f, 361.09f, 132.4f, 361.09f, 148.9f)
curveTo(361.09f, 165.4f, 366.89f, 179.57f, 378.8f, 191.47f)
curveTo(390.7f, 203.38f, 404.86f, 209.18f, 421.36f, 209.18f)
horizontalLineTo(459.04f)
verticalLineTo(224.24f)
horizontalLineTo(421.36f)
curveTo(400.64f, 224.24f, 382.94f, 216.71f, 368.25f, 202.02f)
curveTo(353.55f, 187.33f, 346.02f, 169.62f, 346.02f, 148.9f)
curveTo(346.02f, 128.18f, 353.55f, 110.47f, 368.25f, 95.78f)
curveTo(382.94f, 81.09f, 400.64f, 73.56f, 421.36f, 73.56f)
close()
}
}.build()
return _NewServer!!
}
@Suppress("ObjectPropertyName")
private var _NewServer: ImageVector? = null
@Preview(showBackground = true)
@Composable
fun NewServerPreview() {
Image(
imageVector = NewServer,
contentDescription = null
)
}

View File

@ -517,7 +517,11 @@ fun ChatRouterScreen(
showAddServerSheet = false showAddServerSheet = false
} }
) { ) {
AddServerSheet() AddServerSheet(
onDismiss = {
showAddServerSheet = false
}
)
} }
} }

View File

@ -2,165 +2,493 @@ package chat.revolt.sheets
import android.content.Intent import android.content.Intent
import android.util.Log import android.util.Log
import android.widget.Toast import androidx.compose.animation.AnimatedContent
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.animation.animateContentSize
import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ExitToApp import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Build import androidx.compose.material3.Button
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.net.toUri import androidx.core.net.toUri
import chat.revolt.R import chat.revolt.R
import chat.revolt.activities.InviteActivity import chat.revolt.activities.InviteActivity
import chat.revolt.api.REVOLT_APP import chat.revolt.api.REVOLT_APP
import chat.revolt.composables.generic.FormTextField import chat.revolt.api.routes.server.createServer
import chat.revolt.composables.generic.SheetButton import chat.revolt.callbacks.Action
import chat.revolt.composables.generic.SheetHeaderPadding import chat.revolt.callbacks.ActionChannel
import chat.revolt.composables.sheets.SheetSelection
import chat.revolt.composables.vectorassets.CreateServer
import chat.revolt.composables.vectorassets.JoinServer
import chat.revolt.composables.vectorassets.NewServer
import chat.revolt.material.EasingTokens
import chat.revolt.screens.chat.ChatRouterDestination
import chat.revolt.ui.theme.FragmentMono
import kotlinx.coroutines.launch
import logcat.asLog
import logcat.logcat
enum class AddServerSheetStep(val animationValue: Int) {
Initial(0),
JoinFromInvite(1),
CreateServer(1)
}
@Composable @Composable
fun AddServerSheet() { fun AddServerSheet(onDismiss: () -> Unit) {
var currentStep by remember { mutableStateOf(AddServerSheetStep.Initial) }
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope()
val joinFromInviteModalOpen = remember { mutableStateOf(false) }
Column( Column(
modifier = Modifier modifier = Modifier
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
if (joinFromInviteModalOpen.value) { AnimatedContent(
JoinFromInviteModal( currentStep,
onDismiss = { transitionSpec = {
joinFromInviteModalOpen.value = false if (targetState.animationValue > initialState.animationValue) {
(slideInHorizontally(
animationSpec = tween(300, 0, EasingTokens.EmphasizedDecelerate),
initialOffsetX = { fullWidth -> fullWidth }
) + fadeIn(
animationSpec = tween(300, 0, EasingTokens.EmphasizedDecelerate)
)) togetherWith (slideOutHorizontally(
animationSpec = tween(300, 0, EasingTokens.EmphasizedDecelerate),
targetOffsetX = { fullWidth -> -fullWidth }
) + fadeOut(
animationSpec = tween(300, 0, EasingTokens.EmphasizedDecelerate)
))
} else {
(slideInHorizontally(
animationSpec = tween(300, 0, EasingTokens.EmphasizedDecelerate),
initialOffsetX = { fullWidth -> -fullWidth }
) + fadeIn(
animationSpec = tween(300, 0, EasingTokens.EmphasizedDecelerate)
)) togetherWith (slideOutHorizontally(
animationSpec = tween(300, 0, EasingTokens.EmphasizedDecelerate),
targetOffsetX = { fullWidth -> fullWidth }
) + fadeOut(
animationSpec = tween(300, 0, EasingTokens.EmphasizedDecelerate)
))
} }
},
contentAlignment = Alignment.BottomCenter,
modifier = Modifier.animateContentSize(
animationSpec = tween(150, 0, EasingTokens.EmphasizedDecelerate)
) )
} ) { step ->
Box(
SheetHeaderPadding { modifier = Modifier.padding(16.dp)
Text(
text = stringResource(id = R.string.add_server_sheet_title),
style = MaterialTheme.typography.headlineSmall
)
}
Spacer(modifier = Modifier.height(16.dp))
SheetButton(
headlineContent = {
Text(stringResource(id = R.string.add_server_sheet_join_by_invite))
},
leadingContent = {
Icon(
imageVector = Icons.AutoMirrored.Default.ExitToApp,
contentDescription = null
)
},
onClick = {
joinFromInviteModalOpen.value = true
}
)
SheetButton(
headlineContent = {
Text(stringResource(id = R.string.add_server_sheet_create_new))
},
leadingContent = {
Icon(
imageVector = Icons.Default.Build,
contentDescription = null
)
},
onClick = {
Toast.makeText(
context,
context.getString(
R.string.add_server_sheet_create_new_modal_under_construction
),
Toast.LENGTH_SHORT
).show()
}
)
}
}
@Composable
fun JoinFromInviteModal(onDismiss: () -> Unit) {
val context = LocalContext.current
val inviteCode = remember { mutableStateOf("") }
val inviteActivityResult = rememberLauncherForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
Log.d("InviteActivity", "Result: $result")
}
AlertDialog(
onDismissRequest = onDismiss,
title = {
Text(text = stringResource(id = R.string.add_server_sheet_join_by_invite_modal_title))
},
text = {
Column {
Text(
text = stringResource(
id = R.string.add_server_sheet_join_by_invite_modal_description
)
)
Spacer(modifier = Modifier.height(8.dp))
FormTextField(
label = stringResource(
id = R.string.add_server_sheet_join_by_invite_modal_hint
),
value = inviteCode.value,
onChange = {
inviteCode.value = it
}
)
}
},
confirmButton = {
TextButton(
onClick = {
val intent = Intent(context, InviteActivity::class.java)
intent.data = if (inviteCode.value.startsWith("https://")) {
inviteCode.value.toUri()
} else {
"https://$REVOLT_APP/invite/${inviteCode.value}".toUri()
}
inviteActivityResult.launch(intent)
}
) { ) {
Text( if (step == AddServerSheetStep.Initial) {
text = stringResource(id = R.string.add_server_sheet_join_by_invite_modal_join) Column(
) verticalArrangement = Arrangement.spacedBy(16.dp),
} horizontalAlignment = Alignment.CenterHorizontally,
}, ) {
dismissButton = { Image(
TextButton( imageVector = NewServer,
onClick = { contentDescription = null,
onDismiss() modifier = Modifier
.fillMaxWidth(0.5f)
)
Text(
text = stringResource(id = R.string.add_server_sheet_step_0_title),
style = MaterialTheme.typography.titleLarge,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
Text(
text = stringResource(id = R.string.add_server_sheet_step_0_description),
style = MaterialTheme.typography.bodyMedium,
color = LocalContentColor.current.copy(alpha = 0.7f),
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
SheetSelection(
icon = {
Image(
imageVector = JoinServer,
contentDescription = null,
modifier = Modifier
.size(48.dp)
)
},
title = {
Text(
text = stringResource(id = R.string.add_server_sheet_step_0_join)
)
},
description = {
Text(
text = stringResource(id = R.string.add_server_sheet_step_0_join_description)
)
},
) {
currentStep = AddServerSheetStep.JoinFromInvite
}
SheetSelection(
icon = {
Image(
imageVector = CreateServer,
contentDescription = null,
modifier = Modifier
.size(48.dp)
)
},
title = {
Text(
text = stringResource(id = R.string.add_server_sheet_step_0_create)
)
},
description = {
Text(
text = stringResource(id = R.string.add_server_sheet_step_0_create_description)
)
},
) {
currentStep = AddServerSheetStep.CreateServer
}
}
} else if (step == AddServerSheetStep.JoinFromInvite) {
val inviteState = rememberTextFieldState()
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Box(
contentAlignment = Alignment.CenterStart,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = stringResource(id = R.string.add_server_sheet_step_1j_title),
style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
IconButton(
onClick = {
currentStep = AddServerSheetStep.Initial
}
) {
Icon(
imageVector = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = stringResource(id = R.string.back),
)
}
}
Text(
text = stringResource(id = R.string.add_server_sheet_step_1j_description),
style = MaterialTheme.typography.bodyMedium,
color = LocalContentColor.current.copy(alpha = 0.7f),
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
Text(
text = stringResource(id = R.string.add_server_sheet_step_1j_examples_heading),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.outline,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(id = R.string.add_server_sheet_step_1j_example_1),
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
fontFamily = FragmentMono,
modifier = Modifier
.clip(
MaterialTheme.shapes.small.copy(
topStart = MaterialTheme.shapes.large.topStart,
topEnd = MaterialTheme.shapes.large.topEnd,
)
)
.background(MaterialTheme.colorScheme.surfaceContainerHighest)
.padding(16.dp)
.fillMaxWidth()
)
Text(
text = stringResource(id = R.string.add_server_sheet_step_1j_example_2),
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
fontFamily = FragmentMono,
modifier = Modifier
.clip(
MaterialTheme.shapes.small
)
.background(MaterialTheme.colorScheme.surfaceContainerHighest)
.padding(16.dp)
.fillMaxWidth()
)
Text(
text = stringResource(id = R.string.add_server_sheet_step_1j_example_3),
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
fontFamily = FragmentMono,
modifier = Modifier
.clip(
MaterialTheme.shapes.small.copy(
bottomStart = MaterialTheme.shapes.large.bottomStart,
bottomEnd = MaterialTheme.shapes.large.bottomEnd,
)
)
.background(MaterialTheme.colorScheme.surfaceContainerHighest)
.padding(16.dp)
.fillMaxWidth()
)
}
Spacer(modifier = Modifier.height(8.dp))
TextField(
state = inviteState,
label = {
Text(stringResource(R.string.add_server_sheet_step_1j_label))
},
lineLimits = TextFieldLineLimits.SingleLine,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp, horizontal = 8.dp)
)
Button(
onClick = {
val intent = Intent(context, InviteActivity::class.java)
intent.data = if (inviteState.text.startsWith("https://")) {
try {
inviteState.text.toString().toUri()
} catch (e: Exception) {
Log.e(
"AddServerSheet",
"Invalid URL: ${inviteState.text}",
e
)
return@Button
}
} else {
"https://$REVOLT_APP/invite/${inviteState.text}".toUri()
}
context.startActivity(intent)
onDismiss()
},
modifier = Modifier
.fillMaxWidth()
) {
Text(
text = stringResource(id = R.string.add_server_sheet_step_1j_join)
)
}
}
} else if (step == AddServerSheetStep.CreateServer) {
val serverNameState = rememberTextFieldState()
val serverNameIsBlank = remember(serverNameState.text) {
serverNameState.text.isBlank()
}
var serverNameRangeError by remember { mutableStateOf(false) }
var serverCreationError by remember { mutableStateOf(false) }
LaunchedEffect(serverNameState.text) {
if (serverNameState.text.length > 32) {
serverNameRangeError = true
} else {
serverNameRangeError = false
}
}
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Box(
contentAlignment = Alignment.CenterStart,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = stringResource(id = R.string.add_server_sheet_step_1c_title),
style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
IconButton(
onClick = {
currentStep = AddServerSheetStep.Initial
}
) {
Icon(
imageVector = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = stringResource(id = R.string.back),
)
}
}
Text(
text = stringResource(id = R.string.add_server_sheet_step_1c_description),
style = MaterialTheme.typography.bodyMedium,
color = LocalContentColor.current.copy(alpha = 0.7f),
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
val outline = MaterialTheme.colorScheme.outline
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Canvas(Modifier.size(80.dp)) {
val stroke = Stroke(
width = 4.dp.toPx(),
pathEffect = PathEffect.dashPathEffect(
floatArrayOf(30f, 40f),
0f
)
)
drawCircle(
color = outline,
style = stroke,
radius = size.minDimension / 2 - stroke.width / 2
)
}
Text(
text = when {
serverNameIsBlank -> stringResource(R.string.add_server_sheet_step_1c_name_placeholder)
else -> serverNameState.text.toString()
},
style = MaterialTheme.typography.bodyLarge,
color = LocalContentColor.current.copy(
alpha = when {
serverNameIsBlank -> 0.5f
else -> 1f
}
),
fontWeight = when {
serverNameIsBlank -> FontWeight.Medium
else -> FontWeight.Bold
},
textAlign = TextAlign.Center,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.fillMaxWidth()
)
}
TextField(
state = serverNameState,
label = {
Text(stringResource(R.string.add_server_sheet_step_1c_name))
},
lineLimits = TextFieldLineLimits.SingleLine,
supportingText = {
Text(
when {
serverCreationError -> stringResource(R.string.add_server_sheet_step_1c_error)
serverNameRangeError -> stringResource(
R.string.add_server_sheet_step_1c_name_error_range,
32
)
else -> ""
}
)
},
isError = serverNameRangeError || serverCreationError,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp, horizontal = 8.dp)
)
Button(
onClick = {
serverCreationError = false
scope.launch {
try {
val server = createServer(serverNameState.text.toString())
// Backend should've already created a channel for us to go to
server.channels?.first()?.id?.let {
ActionChannel.send(
Action.ChatNavigate(
ChatRouterDestination.Channel(it)
)
)
}
onDismiss()
} catch (e: Exception) {
serverCreationError = true
logcat { "Error creating server: ${e.asLog()}" }
}
}
},
enabled = !serverNameIsBlank && !serverNameRangeError,
modifier = Modifier
.fillMaxWidth()
) {
Text(
text = stringResource(id = R.string.add_server_sheet_step_1c_create)
)
}
}
} }
) {
Text(text = stringResource(id = R.string.cancel))
} }
} }
) }
} }

View File

@ -435,15 +435,27 @@
<string name="user_badge_reserved_relevant_joke_badge_1" translatable="false">sus</string> <string name="user_badge_reserved_relevant_joke_badge_1" translatable="false">sus</string>
<string name="user_badge_reserved_relevant_joke_badge_2" translatable="false">It\'s Morbin Time</string> <string name="user_badge_reserved_relevant_joke_badge_2" translatable="false">It\'s Morbin Time</string>
<string name="add_server_sheet_title">Add a server</string> <string name="add_server_sheet_step_0_title">Add Server</string>
<string name="add_server_sheet_join_by_invite">Join by invite code or link</string> <string name="add_server_sheet_step_0_description">A server is where the magic happens. Chat with your friends, share memes, build communities, and more.</string>
<string name="add_server_sheet_join_by_invite_modal_title">Invite code or link</string> <string name="add_server_sheet_step_0_join">Join by invite code or link</string>
<string name="add_server_sheet_join_by_invite_modal_description">Enter a link like rvlt.gg/Testers or an invite code like Testers</string> <string name="add_server_sheet_step_0_join_description">You already have an invite code to join an existing server.</string>
<string name="add_server_sheet_join_by_invite_modal_hint">Invite code or link</string> <string name="add_server_sheet_step_0_create">Create a new server</string>
<string name="add_server_sheet_join_by_invite_modal_join">Join</string> <string name="add_server_sheet_step_0_create_description">You want to create a completely new server from scratch.</string>
<string name="add_server_sheet_create_new">Create a new server</string> <string name="add_server_sheet_step_1j_title">Join with an Invite</string>
<string name="add_server_sheet_create_new_modal_title">Create a new server</string> <string name="add_server_sheet_step_1j_description">If you have an invite code or link, you can join a server right now.</string>
<string name="add_server_sheet_create_new_modal_under_construction">This feature is currently under construction.</string> <string name="add_server_sheet_step_1j_examples_heading">Examples of invite codes include:</string>
<string name="add_server_sheet_step_1j_example_1" translatable="false">rvlt.gg/Testers</string>
<string name="add_server_sheet_step_1j_example_2" translatable="false">Testers</string>
<string name="add_server_sheet_step_1j_example_3" translatable="false">app.revolt.chat/invite/Testers</string>
<string name="add_server_sheet_step_1j_label">Invite code or link</string>
<string name="add_server_sheet_step_1j_join">Join</string>
<string name="add_server_sheet_step_1c_title">Create a Server</string>
<string name="add_server_sheet_step_1c_description">You can always customise the name or icon and invite your friends later.</string>
<string name="add_server_sheet_step_1c_name">Server Name</string>
<string name="add_server_sheet_step_1c_name_placeholder">My Server</string>
<string name="add_server_sheet_step_1c_name_error_range">Server name can be up to %1$d characters in length.</string>
<string name="add_server_sheet_step_1c_error">An error occurred while creating your server. Please try again later.</string>
<string name="add_server_sheet_step_1c_create">Create</string>
<string name="discover">Discover Revolt</string> <string name="discover">Discover Revolt</string>