feat: server reporting
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
46b75c3ed3
commit
f9b778e3c4
|
|
@ -0,0 +1,131 @@
|
||||||
|
package chat.revolt.components.screens.settings
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
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.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import chat.revolt.R
|
||||||
|
import chat.revolt.api.REVOLT_FILES
|
||||||
|
import chat.revolt.api.schemas.Server
|
||||||
|
import chat.revolt.api.schemas.ServerFlags
|
||||||
|
import chat.revolt.api.schemas.has
|
||||||
|
import chat.revolt.components.generic.IconPlaceholder
|
||||||
|
import chat.revolt.components.generic.RemoteImage
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ServerOverview(server: Server) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(MaterialTheme.shapes.large),
|
||||||
|
contentAlignment = Alignment.BottomStart
|
||||||
|
) {
|
||||||
|
server.banner?.let {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(Color.Black.copy(alpha = 0.25f))
|
||||||
|
.height(166.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
RemoteImage(
|
||||||
|
url = "$REVOLT_FILES/banners/${it.id}",
|
||||||
|
description = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.height(166.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
contentScale = ContentScale.FillWidth
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(
|
||||||
|
Brush.verticalGradient(
|
||||||
|
listOf(
|
||||||
|
Color.Transparent,
|
||||||
|
Color.Black.copy(alpha = 0.7f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.height(166.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
) {
|
||||||
|
server.icon?.let {
|
||||||
|
RemoteImage(
|
||||||
|
url = "$REVOLT_FILES/icons/${it.id}/server.png?max_side=256",
|
||||||
|
description = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.height(48.dp)
|
||||||
|
.width(48.dp),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
} ?: run {
|
||||||
|
IconPlaceholder(
|
||||||
|
name = server.name ?: stringResource(R.string.unknown),
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.height(48.dp)
|
||||||
|
.width(48.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
|
||||||
|
CompositionLocalProvider(LocalContentColor provides Color.White) {
|
||||||
|
if (server.flags has ServerFlags.Official) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.ic_revolt_decagram_24dp),
|
||||||
|
contentDescription = stringResource(R.string.server_flag_official),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 8.dp)
|
||||||
|
.size(24.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (server.flags has ServerFlags.Verified) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.ic_check_decagram_24dp),
|
||||||
|
contentDescription = stringResource(R.string.server_flag_verified),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 8.dp)
|
||||||
|
.size(24.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = server.name ?: stringResource(R.string.unknown),
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
fontSize = 16.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -94,6 +94,7 @@ import chat.revolt.internals.Changelogs
|
||||||
import chat.revolt.ndk.Pipebomb
|
import chat.revolt.ndk.Pipebomb
|
||||||
import chat.revolt.persistence.KVStorage
|
import chat.revolt.persistence.KVStorage
|
||||||
import chat.revolt.screens.chat.dialogs.safety.ReportMessageDialog
|
import chat.revolt.screens.chat.dialogs.safety.ReportMessageDialog
|
||||||
|
import chat.revolt.screens.chat.dialogs.safety.ReportServerDialog
|
||||||
import chat.revolt.screens.chat.dialogs.safety.ReportUserDialog
|
import chat.revolt.screens.chat.dialogs.safety.ReportUserDialog
|
||||||
import chat.revolt.screens.chat.views.FriendsScreen
|
import chat.revolt.screens.chat.views.FriendsScreen
|
||||||
import chat.revolt.screens.chat.views.HomeScreen
|
import chat.revolt.screens.chat.views.HomeScreen
|
||||||
|
|
@ -590,6 +591,9 @@ fun ChatRouterScreen(
|
||||||
onHideSheet = {
|
onHideSheet = {
|
||||||
serverContextSheetState.hide()
|
serverContextSheetState.hide()
|
||||||
showServerContextSheet = false
|
showServerContextSheet = false
|
||||||
|
},
|
||||||
|
onReportServer = {
|
||||||
|
navController.navigate("report/server/${serverContextSheetTarget}")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -1088,6 +1092,16 @@ fun ChannelNavigator(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dialog("report/server/{serverId}") { backStackEntry ->
|
||||||
|
val serverId = backStackEntry.arguments?.getString("serverId")
|
||||||
|
if (serverId != null) {
|
||||||
|
ReportServerDialog(
|
||||||
|
navController = navController,
|
||||||
|
serverId = serverId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dialog("report/user/{userId}") { backStackEntry ->
|
dialog("report/user/{userId}") { backStackEntry ->
|
||||||
val userId = backStackEntry.arguments?.getString("userId")
|
val userId = backStackEntry.arguments?.getString("userId")
|
||||||
if (userId != null) {
|
if (userId != null) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,319 @@
|
||||||
|
package chat.revolt.screens.chat.dialogs.safety
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
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.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Check
|
||||||
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||||
|
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
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.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import chat.revolt.R
|
||||||
|
import chat.revolt.api.RevoltAPI
|
||||||
|
import chat.revolt.api.routes.safety.putServerReport
|
||||||
|
import chat.revolt.api.schemas.ContentReportReason
|
||||||
|
import chat.revolt.components.generic.FormTextField
|
||||||
|
import chat.revolt.components.screens.settings.ServerOverview
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
enum class ServerReportFlowState {
|
||||||
|
Reason,
|
||||||
|
Sending,
|
||||||
|
Done,
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ReportServerDialog(navController: NavController, serverId: String) {
|
||||||
|
val server = RevoltAPI.serverCache[serverId]
|
||||||
|
if (server == null) {
|
||||||
|
navController.popBackStack()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val state = remember { mutableStateOf(ServerReportFlowState.Reason) }
|
||||||
|
|
||||||
|
val selectedReason = remember { mutableStateOf("Illegal") }
|
||||||
|
val userAddedContext = remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
when (state.value) {
|
||||||
|
ServerReportFlowState.Reason -> {
|
||||||
|
val reasons = mapOf(
|
||||||
|
"Illegal" to stringResource(id = R.string.report_reason_content_illegal),
|
||||||
|
"IllegalGoods" to stringResource(id = R.string.report_reason_content_illegal_goods),
|
||||||
|
"IllegalExtortion" to stringResource(id = R.string.report_reason_content_illegal_extortion),
|
||||||
|
"IllegalPornography" to stringResource(id = R.string.report_reason_content_illegal_pornography),
|
||||||
|
"IllegalHacking" to stringResource(id = R.string.report_reason_content_illegal_hacking),
|
||||||
|
"ExtremeViolence" to stringResource(id = R.string.report_reason_content_extreme_violence),
|
||||||
|
"PromotesHarm" to stringResource(id = R.string.report_reason_content_promotes_harm),
|
||||||
|
"UnsolicitedSpam" to stringResource(id = R.string.report_reason_content_unsolicited_spam),
|
||||||
|
"Raid" to stringResource(id = R.string.report_reason_content_raid),
|
||||||
|
"SpamAbuse" to stringResource(id = R.string.report_reason_content_spam_abuse),
|
||||||
|
"ScamsFraud" to stringResource(id = R.string.report_reason_content_scams_fraud),
|
||||||
|
"Malware" to stringResource(id = R.string.report_reason_content_malware),
|
||||||
|
"Harassment" to stringResource(id = R.string.report_reason_content_harassment),
|
||||||
|
"NoneSpecified" to stringResource(id = R.string.report_reason_content_other)
|
||||||
|
)
|
||||||
|
val reasonDropdownExpanded = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
// nothing - prevent mistaps from closing the dialog
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.report_server_heading),
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Column {
|
||||||
|
Text(text = stringResource(id = R.string.report_server))
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.report_server_preview),
|
||||||
|
fontSize = 12.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
|
ServerOverview(server)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
ExposedDropdownMenuBox(
|
||||||
|
expanded = reasonDropdownExpanded.value,
|
||||||
|
onExpandedChange = {
|
||||||
|
reasonDropdownExpanded.value = it
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
|
value = reasons[selectedReason.value]
|
||||||
|
?: stringResource(id = R.string.unknown),
|
||||||
|
readOnly = true,
|
||||||
|
onValueChange = {},
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.report_reason)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
trailingIcon = {
|
||||||
|
ExposedDropdownMenuDefaults.TrailingIcon(expanded = reasonDropdownExpanded.value)
|
||||||
|
},
|
||||||
|
colors = ExposedDropdownMenuDefaults.textFieldColors(),
|
||||||
|
modifier = Modifier.menuAnchor()
|
||||||
|
)
|
||||||
|
|
||||||
|
ExposedDropdownMenu(
|
||||||
|
expanded = reasonDropdownExpanded.value,
|
||||||
|
onDismissRequest = {
|
||||||
|
reasonDropdownExpanded.value = false
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
reasons.forEach { (key, value) ->
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = {
|
||||||
|
Text(text = value)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
selectedReason.value = key
|
||||||
|
reasonDropdownExpanded.value = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
FormTextField(
|
||||||
|
value = userAddedContext.value,
|
||||||
|
label = stringResource(id = R.string.report_reason_additional),
|
||||||
|
onChange = {
|
||||||
|
userAddedContext.value = it
|
||||||
|
},
|
||||||
|
supportingText = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(
|
||||||
|
id = R.string.report_reason_additional_hint
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
navController.popBackStack()
|
||||||
|
},
|
||||||
|
modifier = Modifier.testTag("report_cancel")
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(id = R.string.report_cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
state.value = ServerReportFlowState.Sending
|
||||||
|
},
|
||||||
|
modifier = Modifier.testTag("report_send")
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(id = R.string.report_submit))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerReportFlowState.Sending -> {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {},
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.report_submitting),
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator()
|
||||||
|
LaunchedEffect(true) {
|
||||||
|
launch {
|
||||||
|
try {
|
||||||
|
Log.d("ReportServerDialog", "Reporting server $serverId")
|
||||||
|
putServerReport(
|
||||||
|
serverId,
|
||||||
|
ContentReportReason.valueOf(selectedReason.value),
|
||||||
|
userAddedContext.value
|
||||||
|
)
|
||||||
|
state.value = ServerReportFlowState.Done
|
||||||
|
} catch (e: Error) {
|
||||||
|
state.value = ServerReportFlowState.Error
|
||||||
|
Log.e("ReportServerDialog", "Failed to report server", e)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {},
|
||||||
|
confirmButton = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerReportFlowState.Done -> {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
navController.popBackStack()
|
||||||
|
},
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Check,
|
||||||
|
contentDescription = null, // decorative
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.report_submit_success),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Column {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.report_submit_thanks),
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
navController.popBackStack()
|
||||||
|
},
|
||||||
|
modifier = Modifier.testTag("report_close")
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(id = R.string.report_submit_close))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerReportFlowState.Error -> {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
navController.popBackStack()
|
||||||
|
},
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Close,
|
||||||
|
contentDescription = null, // decorative
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.report_submit_error_header),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.report_submit_error),
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
navController.popBackStack()
|
||||||
|
},
|
||||||
|
modifier = Modifier.testTag("report_error_ok")
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(id = R.string.ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,28 +1,21 @@
|
||||||
package chat.revolt.sheets
|
package chat.revolt.sheets
|
||||||
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
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.padding
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
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.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.runtime.getValue
|
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
|
||||||
|
|
@ -30,32 +23,27 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.graphics.Brush
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.platform.LocalClipboardManager
|
import androidx.compose.ui.platform.LocalClipboardManager
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import chat.revolt.R
|
import chat.revolt.R
|
||||||
import chat.revolt.api.REVOLT_FILES
|
|
||||||
import chat.revolt.api.RevoltAPI
|
import chat.revolt.api.RevoltAPI
|
||||||
import chat.revolt.api.routes.server.leaveOrDeleteServer
|
import chat.revolt.api.routes.server.leaveOrDeleteServer
|
||||||
import chat.revolt.api.schemas.ServerFlags
|
|
||||||
import chat.revolt.api.schemas.has
|
|
||||||
import chat.revolt.components.generic.IconPlaceholder
|
|
||||||
import chat.revolt.components.generic.RemoteImage
|
|
||||||
import chat.revolt.components.generic.SheetClickable
|
import chat.revolt.components.generic.SheetClickable
|
||||||
import chat.revolt.components.generic.UIMarkdown
|
import chat.revolt.components.generic.UIMarkdown
|
||||||
|
import chat.revolt.components.screens.settings.ServerOverview
|
||||||
import chat.revolt.internals.Platform
|
import chat.revolt.internals.Platform
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ServerContextSheet(serverId: String, onHideSheet: suspend () -> Unit) {
|
fun ServerContextSheet(
|
||||||
|
serverId: String,
|
||||||
|
onReportServer: () -> Unit,
|
||||||
|
onHideSheet: suspend () -> Unit
|
||||||
|
) {
|
||||||
val server = RevoltAPI.serverCache[serverId]
|
val server = RevoltAPI.serverCache[serverId]
|
||||||
|
|
||||||
if (server == null) {
|
if (server == null) {
|
||||||
|
|
@ -152,98 +140,7 @@ fun ServerContextSheet(serverId: String, onHideSheet: suspend () -> Unit) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 0.dp, bottom = 16.dp)
|
modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 0.dp, bottom = 16.dp)
|
||||||
) {
|
) {
|
||||||
Box(
|
ServerOverview(server)
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clip(MaterialTheme.shapes.large),
|
|
||||||
contentAlignment = Alignment.BottomStart
|
|
||||||
) {
|
|
||||||
server.banner?.let {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.background(Color.Black.copy(alpha = 0.25f))
|
|
||||||
.height(166.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
)
|
|
||||||
|
|
||||||
RemoteImage(
|
|
||||||
url = "$REVOLT_FILES/banners/${it.id}",
|
|
||||||
description = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.height(166.dp)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
contentScale = ContentScale.FillWidth
|
|
||||||
)
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.background(
|
|
||||||
Brush.verticalGradient(
|
|
||||||
listOf(
|
|
||||||
Color.Transparent,
|
|
||||||
Color.Black.copy(alpha = 0.7f)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.height(166.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier.padding(16.dp)
|
|
||||||
) {
|
|
||||||
server.icon?.let {
|
|
||||||
RemoteImage(
|
|
||||||
url = "$REVOLT_FILES/icons/${it.id}/server.png?max_side=256",
|
|
||||||
description = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(CircleShape)
|
|
||||||
.height(48.dp)
|
|
||||||
.width(48.dp),
|
|
||||||
contentScale = ContentScale.Crop
|
|
||||||
)
|
|
||||||
} ?: run {
|
|
||||||
IconPlaceholder(
|
|
||||||
name = server.name ?: stringResource(R.string.unknown),
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(CircleShape)
|
|
||||||
.height(48.dp)
|
|
||||||
.width(48.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
|
||||||
|
|
||||||
CompositionLocalProvider(LocalContentColor provides Color.White) {
|
|
||||||
if (server.flags has ServerFlags.Official) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.drawable.ic_revolt_decagram_24dp),
|
|
||||||
contentDescription = stringResource(R.string.server_flag_official),
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(end = 8.dp)
|
|
||||||
.size(24.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (server.flags has ServerFlags.Verified) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.drawable.ic_check_decagram_24dp),
|
|
||||||
contentDescription = stringResource(R.string.server_flag_verified),
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(end = 8.dp)
|
|
||||||
.size(24.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = server.name ?: stringResource(R.string.unknown),
|
|
||||||
style = MaterialTheme.typography.labelLarge,
|
|
||||||
fontSize = 16.sp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(horizontal = 4.dp)
|
modifier = Modifier.padding(horizontal = 4.dp)
|
||||||
|
|
@ -321,6 +218,21 @@ fun ServerContextSheet(serverId: String, onHideSheet: suspend () -> Unit) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (server.owner != RevoltAPI.selfId) {
|
if (server.owner != RevoltAPI.selfId) {
|
||||||
|
SheetClickable(icon = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.ic_flag_24dp),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = it
|
||||||
|
)
|
||||||
|
}, label = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.server_context_sheet_actions_report),
|
||||||
|
style = it
|
||||||
|
)
|
||||||
|
}, dangerous = true) {
|
||||||
|
onReportServer()
|
||||||
|
}
|
||||||
|
|
||||||
SheetClickable(icon = {
|
SheetClickable(icon = {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = R.drawable.ic_arrow_left_bold_box_24dp),
|
painter = painterResource(id = R.drawable.ic_arrow_left_bold_box_24dp),
|
||||||
|
|
|
||||||
|
|
@ -295,6 +295,7 @@
|
||||||
<string name="server_context_sheet_actions_leave_confirm_yes">Leave</string>
|
<string name="server_context_sheet_actions_leave_confirm_yes">Leave</string>
|
||||||
<string name="server_context_sheet_actions_leave_confirm_no">Stay</string>
|
<string name="server_context_sheet_actions_leave_confirm_no">Stay</string>
|
||||||
<string name="server_context_sheet_actions_leave_silently">Leave Silently</string>
|
<string name="server_context_sheet_actions_leave_silently">Leave Silently</string>
|
||||||
|
<string name="server_context_sheet_actions_report">Report</string>
|
||||||
|
|
||||||
<string name="user_info_sheet_user_not_found">Can\'t resolve this user</string>
|
<string name="user_info_sheet_user_not_found">Can\'t resolve this user</string>
|
||||||
<string name="user_info_sheet_user_not_found_description">This user may have been deleted or you may not have permission to view them.</string>
|
<string name="user_info_sheet_user_not_found_description">This user may have been deleted or you may not have permission to view them.</string>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue