parent
0a5676fca6
commit
b68267c1e7
|
|
@ -17,6 +17,7 @@ import io.ktor.client.request.setBody
|
||||||
import io.ktor.client.statement.bodyAsText
|
import io.ktor.client.statement.bodyAsText
|
||||||
import io.ktor.http.ContentType
|
import io.ktor.http.ContentType
|
||||||
import io.ktor.http.contentType
|
import io.ktor.http.contentType
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.SerializationException
|
import kotlinx.serialization.SerializationException
|
||||||
import kotlinx.serialization.builtins.ListSerializer
|
import kotlinx.serialization.builtins.ListSerializer
|
||||||
|
|
||||||
|
|
@ -78,7 +79,19 @@ data class EditMessageBody(
|
||||||
val content: String?
|
val content: String?
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun sendMessage(
|
@kotlinx.serialization.Serializable
|
||||||
|
data class CreateInviteResponse(
|
||||||
|
val type: String,
|
||||||
|
@SerialName("_id")
|
||||||
|
val id: String,
|
||||||
|
val server: String,
|
||||||
|
val creator: String,
|
||||||
|
val channel: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend
|
||||||
|
|
||||||
|
fun sendMessage(
|
||||||
channelId: String,
|
channelId: String,
|
||||||
content: String,
|
content: String,
|
||||||
nonce: String? = ULID.makeNext(),
|
nonce: String? = ULID.makeNext(),
|
||||||
|
|
@ -143,3 +156,13 @@ suspend fun fetchGroupParticipants(channelId: String): List<User> {
|
||||||
response
|
response
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun createInvite(channelId: String): CreateInviteResponse {
|
||||||
|
val response = RevoltHttp.post("/channels/$channelId/invites")
|
||||||
|
.bodyAsText()
|
||||||
|
|
||||||
|
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response)
|
||||||
|
if (error.type != "Server") throw Error(error.type)
|
||||||
|
|
||||||
|
return RevoltJson.decodeFromString(CreateInviteResponse.serializer(), response)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,195 @@
|
||||||
|
package chat.revolt.screens.chat.dialogs
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
|
import androidx.compose.animation.slideOutVertically
|
||||||
|
import androidx.compose.animation.togetherWith
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
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.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.platform.LocalClipboardManager
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.Font
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import chat.revolt.R
|
||||||
|
import chat.revolt.api.REVOLT_INVITES
|
||||||
|
import chat.revolt.api.RevoltAPI
|
||||||
|
import chat.revolt.api.routes.channel.createInvite
|
||||||
|
import chat.revolt.internals.Platform
|
||||||
|
|
||||||
|
private val inviteChars = ('a'..'z') + ('A'..'Z') + ('0'..'9')
|
||||||
|
|
||||||
|
private fun placeholderInviteCode(): String {
|
||||||
|
return (1..8)
|
||||||
|
.map { inviteChars.random() }
|
||||||
|
.joinToString("")
|
||||||
|
}
|
||||||
|
|
||||||
|
data class InviteCodeChar(
|
||||||
|
val char: Char,
|
||||||
|
val isActual: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
operator fun InviteCodeChar.compareTo(other: InviteCodeChar): Int {
|
||||||
|
return char.compareTo(other.char)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun InviteDialog(channelId: String, onDismissRequest: () -> Unit) {
|
||||||
|
val channel = RevoltAPI.channelCache[channelId]
|
||||||
|
|
||||||
|
if (channel == null) {
|
||||||
|
onDismissRequest()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var isActual by remember { mutableStateOf(false) }
|
||||||
|
var inviteCode by remember { mutableStateOf(placeholderInviteCode()) }
|
||||||
|
|
||||||
|
val invitePrefixOpacity by animateFloatAsState(
|
||||||
|
targetValue = if (isActual) 1f else 0f,
|
||||||
|
label = "Invite prefix opacity"
|
||||||
|
)
|
||||||
|
|
||||||
|
val clipboardManager = LocalClipboardManager.current
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
try {
|
||||||
|
val invite = createInvite(channelId)
|
||||||
|
isActual = true
|
||||||
|
inviteCode = invite.id
|
||||||
|
} catch (e: Error) {
|
||||||
|
isActual = true
|
||||||
|
inviteCode = "error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BoxWithConstraints {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(MaterialTheme.shapes.large)
|
||||||
|
.background(MaterialTheme.colorScheme.surface)
|
||||||
|
.padding(24.dp)
|
||||||
|
.width(maxWidth * 0.85f)
|
||||||
|
.heightIn(max = maxHeight * 0.85f)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
stringResource(
|
||||||
|
R.string.invite_dialog_header,
|
||||||
|
channel.name ?: stringResource(R.string.unknown)
|
||||||
|
),
|
||||||
|
style = MaterialTheme.typography.headlineMedium
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
(Uri.parse(REVOLT_INVITES).host ?: "rvlt.gg") + "/",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.alpha(invitePrefixOpacity)
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
inviteCode
|
||||||
|
.map { InviteCodeChar(it, isActual) }
|
||||||
|
.forEach {
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = it,
|
||||||
|
transitionSpec = {
|
||||||
|
if (targetState > initialState) {
|
||||||
|
slideInVertically { -it } togetherWith slideOutVertically { it }
|
||||||
|
} else {
|
||||||
|
slideInVertically { it } togetherWith slideOutVertically { -it }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label = "Invite code char"
|
||||||
|
) { state ->
|
||||||
|
Text(
|
||||||
|
state.char.toString(),
|
||||||
|
style = MaterialTheme.typography.displayLarge,
|
||||||
|
fontFamily = FontFamily(Font(R.font.jetbrainsmono_regular)),
|
||||||
|
modifier = Modifier
|
||||||
|
.alpha(if (state.isActual) 1f else 0f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.invite_dialog_description),
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.End
|
||||||
|
) {
|
||||||
|
TextButton(onClick = {
|
||||||
|
onDismissRequest()
|
||||||
|
}) {
|
||||||
|
Text(stringResource(R.string.invite_dialog_close))
|
||||||
|
}
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
Button(onClick = {
|
||||||
|
clipboardManager.setText(AnnotatedString.Builder().apply {
|
||||||
|
append(REVOLT_INVITES)
|
||||||
|
append("/")
|
||||||
|
append(inviteCode)
|
||||||
|
}.toAnnotatedString())
|
||||||
|
|
||||||
|
if (Platform.needsShowClipboardNotification()) {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.copied),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text(stringResource(R.string.invite_dialog_copy))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -28,6 +28,7 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
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 androidx.compose.ui.window.Dialog
|
||||||
import chat.revolt.R
|
import chat.revolt.R
|
||||||
import chat.revolt.api.RevoltAPI
|
import chat.revolt.api.RevoltAPI
|
||||||
import chat.revolt.api.internals.ChannelUtils
|
import chat.revolt.api.internals.ChannelUtils
|
||||||
|
|
@ -37,12 +38,14 @@ import chat.revolt.api.internals.has
|
||||||
import chat.revolt.api.schemas.ChannelType
|
import chat.revolt.api.schemas.ChannelType
|
||||||
import chat.revolt.components.generic.SheetClickable
|
import chat.revolt.components.generic.SheetClickable
|
||||||
import chat.revolt.components.screens.chat.ChannelSheetHeader
|
import chat.revolt.components.screens.chat.ChannelSheetHeader
|
||||||
|
import chat.revolt.screens.chat.dialogs.InviteDialog
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ChannelInfoSheet(channelId: String) {
|
fun ChannelInfoSheet(channelId: String) {
|
||||||
val channel = RevoltAPI.channelCache[channelId]
|
val channel = RevoltAPI.channelCache[channelId]
|
||||||
var memberListSheetShown by remember { mutableStateOf(false) }
|
var memberListSheetShown by remember { mutableStateOf(false) }
|
||||||
|
var inviteDialogShown by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
if (memberListSheetShown) {
|
if (memberListSheetShown) {
|
||||||
val memberListSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
val memberListSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||||
|
|
@ -60,6 +63,21 @@ fun ChannelInfoSheet(channelId: String) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (inviteDialogShown) {
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
inviteDialogShown = false
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
InviteDialog(
|
||||||
|
channelId = channelId,
|
||||||
|
onDismissRequest = {
|
||||||
|
inviteDialogShown = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (channel == null) {
|
if (channel == null) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -167,6 +185,7 @@ fun ChannelInfoSheet(channelId: String) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
inviteDialogShown = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -239,6 +239,11 @@
|
||||||
<string name="channel_info_sheet_options_add">Add members</string>
|
<string name="channel_info_sheet_options_add">Add members</string>
|
||||||
<string name="channel_info_sheet_options_notifications_manage">Manage notifications</string>
|
<string name="channel_info_sheet_options_notifications_manage">Manage notifications</string>
|
||||||
|
|
||||||
|
<string name="invite_dialog_header">Invite to #%1$s</string>
|
||||||
|
<string name="invite_dialog_description">Send this link to invite people to this channel.</string>
|
||||||
|
<string name="invite_dialog_copy">Copy</string>
|
||||||
|
<string name="invite_dialog_close">Close</string>
|
||||||
|
|
||||||
<string name="message_context_sheet_actions_copy">Copy</string>
|
<string name="message_context_sheet_actions_copy">Copy</string>
|
||||||
<string name="message_context_sheet_actions_copy_failed_empty">Message is empty, nothing to copy</string>
|
<string name="message_context_sheet_actions_copy_failed_empty">Message is empty, nothing to copy</string>
|
||||||
<string name="message_context_sheet_actions_reply">Reply</string>
|
<string name="message_context_sheet_actions_reply">Reply</string>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue