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.api
import chat.revolt.api.schemas.Member
import chat.revolt.api.schemas.ServerWithChannelObjects
import chat.revolt.api.schemas.User
import io.ktor.client.request.delete
import io.ktor.client.request.get
import io.ktor.client.request.parameter
import io.ktor.client.request.post
import io.ktor.client.request.put
import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
@ -91,3 +94,31 @@ suspend fun leaveOrDeleteServer(serverId: String, leaveSilently: Boolean = false
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 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
}
) {
AddServerSheet()
AddServerSheet(
onDismiss = {
showAddServerSheet = false
}
)
}
}

View File

@ -2,165 +2,493 @@ package chat.revolt.sheets
import android.content.Intent
import android.util.Log
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.animateContentSize
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.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
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.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ExitToApp
import androidx.compose.material.icons.filled.Build
import androidx.compose.material3.AlertDialog
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
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.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.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.core.net.toUri
import chat.revolt.R
import chat.revolt.activities.InviteActivity
import chat.revolt.api.REVOLT_APP
import chat.revolt.composables.generic.FormTextField
import chat.revolt.composables.generic.SheetButton
import chat.revolt.composables.generic.SheetHeaderPadding
import chat.revolt.api.routes.server.createServer
import chat.revolt.callbacks.Action
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
fun AddServerSheet() {
fun AddServerSheet(onDismiss: () -> Unit) {
var currentStep by remember { mutableStateOf(AddServerSheetStep.Initial) }
val context = LocalContext.current
val joinFromInviteModalOpen = remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
) {
if (joinFromInviteModalOpen.value) {
JoinFromInviteModal(
onDismiss = {
joinFromInviteModalOpen.value = false
AnimatedContent(
currentStep,
transitionSpec = {
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)
)
}
SheetHeaderPadding {
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)
}
) { step ->
Box(
modifier = Modifier.padding(16.dp)
) {
Text(
text = stringResource(id = R.string.add_server_sheet_join_by_invite_modal_join)
)
}
},
dismissButton = {
TextButton(
onClick = {
onDismiss()
if (step == AddServerSheetStep.Initial) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Image(
imageVector = NewServer,
contentDescription = null,
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_2" translatable="false">It\'s Morbin Time</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_modal_title">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_join_by_invite_modal_hint">Invite code or link</string>
<string name="add_server_sheet_join_by_invite_modal_join">Join</string>
<string name="add_server_sheet_create_new">Create a new server</string>
<string name="add_server_sheet_create_new_modal_title">Create a new server</string>
<string name="add_server_sheet_create_new_modal_under_construction">This feature is currently under construction.</string>
<string name="add_server_sheet_step_0_title">Add Server</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_step_0_join">Join by invite code or link</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_step_0_create">Create a new server</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_step_1j_title">Join with an Invite</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_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>