feat: message reporting and block route
This commit is contained in:
parent
5cf0943e26
commit
c38fc51e23
|
|
@ -0,0 +1 @@
|
||||||
|
Revolt
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
package chat.revolt.api.routes.safety
|
||||||
|
|
||||||
|
import chat.revolt.api.RevoltAPI
|
||||||
|
import chat.revolt.api.RevoltError
|
||||||
|
import chat.revolt.api.RevoltHttp
|
||||||
|
import chat.revolt.api.RevoltJson
|
||||||
|
import chat.revolt.api.schemas.*
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.client.statement.*
|
||||||
|
import kotlinx.serialization.SerializationException
|
||||||
|
|
||||||
|
suspend fun putMessageReport(
|
||||||
|
messageId: String,
|
||||||
|
reason: ContentReportReason,
|
||||||
|
additionalContext: String? = null
|
||||||
|
) {
|
||||||
|
val fullMessageReport = FullMessageReport(
|
||||||
|
content = MessageReport(
|
||||||
|
report_reason = reason,
|
||||||
|
id = messageId,
|
||||||
|
),
|
||||||
|
additional_context = additionalContext,
|
||||||
|
)
|
||||||
|
|
||||||
|
val response = RevoltHttp.post("/safety/report") {
|
||||||
|
headers.append(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken)
|
||||||
|
setBody(
|
||||||
|
RevoltJson.encodeToString(
|
||||||
|
FullMessageReport.serializer(),
|
||||||
|
fullMessageReport
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.bodyAsText()
|
||||||
|
|
||||||
|
try {
|
||||||
|
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response)
|
||||||
|
throw Error(error.type)
|
||||||
|
} catch (e: SerializationException) {
|
||||||
|
// Not an error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun putServerReport(
|
||||||
|
serverId: String,
|
||||||
|
reason: ContentReportReason,
|
||||||
|
additionalContext: String? = null
|
||||||
|
) {
|
||||||
|
val fullServerReport = FullServerReport(
|
||||||
|
content = ServerReport(
|
||||||
|
report_reason = reason,
|
||||||
|
id = serverId,
|
||||||
|
),
|
||||||
|
additional_context = additionalContext,
|
||||||
|
)
|
||||||
|
|
||||||
|
val response = RevoltHttp.post("/safety/report") {
|
||||||
|
headers.append(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken)
|
||||||
|
setBody(
|
||||||
|
RevoltJson.encodeToString(
|
||||||
|
FullServerReport.serializer(),
|
||||||
|
fullServerReport
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.bodyAsText()
|
||||||
|
|
||||||
|
try {
|
||||||
|
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response)
|
||||||
|
throw Error(error.type)
|
||||||
|
} catch (e: SerializationException) {
|
||||||
|
// Not an error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun putUserReport(
|
||||||
|
userId: String,
|
||||||
|
reason: UserReportReason,
|
||||||
|
additionalContext: String? = null
|
||||||
|
) {
|
||||||
|
val fullUserReport = FullUserReport(
|
||||||
|
content = UserReport(
|
||||||
|
report_reason = reason,
|
||||||
|
id = userId,
|
||||||
|
),
|
||||||
|
additional_context = additionalContext,
|
||||||
|
)
|
||||||
|
|
||||||
|
val response = RevoltHttp.post("/safety/report") {
|
||||||
|
headers.append(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken)
|
||||||
|
setBody(
|
||||||
|
RevoltJson.encodeToString(
|
||||||
|
FullUserReport.serializer(),
|
||||||
|
fullUserReport
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.bodyAsText()
|
||||||
|
|
||||||
|
try {
|
||||||
|
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response)
|
||||||
|
throw Error(error.type)
|
||||||
|
} catch (e: SerializationException) {
|
||||||
|
// Not an error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package chat.revolt.api.routes.user
|
||||||
|
|
||||||
|
import chat.revolt.api.RevoltAPI
|
||||||
|
import chat.revolt.api.RevoltError
|
||||||
|
import chat.revolt.api.RevoltHttp
|
||||||
|
import chat.revolt.api.RevoltJson
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.client.statement.*
|
||||||
|
import kotlinx.serialization.SerializationException
|
||||||
|
|
||||||
|
suspend fun blockUser(userId: String) {
|
||||||
|
val response = RevoltHttp.put("/users/$userId/block") {
|
||||||
|
headers.append(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken)
|
||||||
|
}
|
||||||
|
.bodyAsText()
|
||||||
|
|
||||||
|
try {
|
||||||
|
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response)
|
||||||
|
throw Error(error.type)
|
||||||
|
} catch (e: SerializationException) {
|
||||||
|
// Not an error
|
||||||
|
}
|
||||||
|
|
||||||
|
val user = RevoltAPI.userCache[userId] ?: return
|
||||||
|
RevoltAPI.userCache[userId] = user.copy(relationship = "Blocked")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun unblockUser(userId: String) {
|
||||||
|
val response = RevoltHttp.delete("/users/$userId/block") {
|
||||||
|
headers.append(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken)
|
||||||
|
}
|
||||||
|
.bodyAsText()
|
||||||
|
|
||||||
|
try {
|
||||||
|
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response)
|
||||||
|
throw Error(error.type)
|
||||||
|
} catch (e: SerializationException) {
|
||||||
|
// Not an error
|
||||||
|
}
|
||||||
|
|
||||||
|
val user = RevoltAPI.userCache[userId] ?: return
|
||||||
|
RevoltAPI.userCache[userId] = user.copy(relationship = "None")
|
||||||
|
}
|
||||||
|
|
@ -40,6 +40,7 @@ data class Message(
|
||||||
edited = partial.edited ?: edited,
|
edited = partial.edited ?: edited,
|
||||||
embeds = partial.embeds ?: embeds,
|
embeds = partial.embeds ?: embeds,
|
||||||
mentions = partial.mentions ?: mentions,
|
mentions = partial.mentions ?: mentions,
|
||||||
|
masquerade = partial.masquerade ?: masquerade,
|
||||||
type = partial.type ?: type,
|
type = partial.type ?: type,
|
||||||
tail = partial.tail ?: tail,
|
tail = partial.tail ?: tail,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
package chat.revolt.api.schemas
|
||||||
|
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class ContentReportReason(val value: String) {
|
||||||
|
NoneSpecified("NoneSpecified"),
|
||||||
|
Illegal("Illegal"),
|
||||||
|
PromotesHarm("PromotesHarm"),
|
||||||
|
SpamAbuse("SpamAbuse"),
|
||||||
|
Malware("Malware"),
|
||||||
|
Harassment("Harassment");
|
||||||
|
|
||||||
|
companion object : KSerializer<ContentReportReason> {
|
||||||
|
override val descriptor: SerialDescriptor
|
||||||
|
get() {
|
||||||
|
return PrimitiveSerialDescriptor(
|
||||||
|
"chat.revolt.api.schemas.ContentReportReason",
|
||||||
|
PrimitiveKind.STRING
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): ContentReportReason =
|
||||||
|
when (val value = decoder.decodeString()) {
|
||||||
|
"NoneSpecified" -> NoneSpecified
|
||||||
|
"Illegal" -> Illegal
|
||||||
|
"PromotesHarm" -> PromotesHarm
|
||||||
|
"SpamAbuse" -> SpamAbuse
|
||||||
|
"Malware" -> Malware
|
||||||
|
"Harassment" -> Harassment
|
||||||
|
else -> throw IllegalArgumentException("Unknown ContentReportReason: $value")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: ContentReportReason) {
|
||||||
|
return encoder.encodeString(value.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class UserReportReason(val value: String) {
|
||||||
|
NoneSpecified("NoneSpecified"),
|
||||||
|
SpamAbuse("SpamAbuse"),
|
||||||
|
InappropriateProfile("InappropriateProfile"),
|
||||||
|
Impersonation("Impersonation"),
|
||||||
|
BanEvasion("BanEvasion"),
|
||||||
|
Underage("Underage");
|
||||||
|
|
||||||
|
companion object : KSerializer<UserReportReason> {
|
||||||
|
override val descriptor: SerialDescriptor
|
||||||
|
get() {
|
||||||
|
return PrimitiveSerialDescriptor(
|
||||||
|
"chat.revolt.api.schemas.UserReportReason",
|
||||||
|
PrimitiveKind.STRING
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): UserReportReason =
|
||||||
|
when (val value = decoder.decodeString()) {
|
||||||
|
"NoneSpecified" -> NoneSpecified
|
||||||
|
"SpamAbuse" -> SpamAbuse
|
||||||
|
"InappropriateProfile" -> InappropriateProfile
|
||||||
|
"Impersonation" -> Impersonation
|
||||||
|
"BanEvasion" -> BanEvasion
|
||||||
|
"Underage" -> Underage
|
||||||
|
else -> throw IllegalArgumentException("Unknown UserReportReason: $value")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: UserReportReason) {
|
||||||
|
return encoder.encodeString(value.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MessageReport(
|
||||||
|
val type: String = "Message",
|
||||||
|
val id: String,
|
||||||
|
val report_reason: ContentReportReason,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class FullMessageReport(
|
||||||
|
val content: MessageReport,
|
||||||
|
val additional_context: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ServerReport(
|
||||||
|
val type: String = "Server",
|
||||||
|
val id: String,
|
||||||
|
val report_reason: ContentReportReason,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class FullServerReport(
|
||||||
|
val content: ServerReport,
|
||||||
|
val additional_context: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UserReport(
|
||||||
|
val type: String = "User",
|
||||||
|
val id: String,
|
||||||
|
val report_reason: UserReportReason,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class FullUserReport(
|
||||||
|
val content: UserReport,
|
||||||
|
val additional_context: String? = null,
|
||||||
|
)
|
||||||
|
|
@ -25,10 +25,7 @@ import androidx.compose.ui.unit.sp
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.*
|
||||||
import androidx.navigation.compose.composable
|
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
|
||||||
import androidx.navigation.compose.rememberNavController
|
|
||||||
import chat.revolt.R
|
import chat.revolt.R
|
||||||
import chat.revolt.api.RevoltAPI
|
import chat.revolt.api.RevoltAPI
|
||||||
import chat.revolt.api.realtime.DisconnectionState
|
import chat.revolt.api.realtime.DisconnectionState
|
||||||
|
|
@ -43,6 +40,7 @@ import chat.revolt.components.screens.chat.drawer.server.DrawerServer
|
||||||
import chat.revolt.components.screens.chat.drawer.server.DrawerServerlikeIcon
|
import chat.revolt.components.screens.chat.drawer.server.DrawerServerlikeIcon
|
||||||
import chat.revolt.components.screens.chat.drawer.server.ServerDrawerSeparator
|
import chat.revolt.components.screens.chat.drawer.server.ServerDrawerSeparator
|
||||||
import chat.revolt.components.screens.chat.rememberDoubleDrawerState
|
import chat.revolt.components.screens.chat.rememberDoubleDrawerState
|
||||||
|
import chat.revolt.screens.chat.dialogs.safety.ReportMessageDialog
|
||||||
import chat.revolt.screens.chat.sheets.ChannelInfoSheet
|
import chat.revolt.screens.chat.sheets.ChannelInfoSheet
|
||||||
import chat.revolt.screens.chat.sheets.MessageContextSheet
|
import chat.revolt.screens.chat.sheets.MessageContextSheet
|
||||||
import chat.revolt.screens.chat.sheets.StatusSheet
|
import chat.revolt.screens.chat.sheets.StatusSheet
|
||||||
|
|
@ -302,6 +300,16 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = vie
|
||||||
bottomSheet("status") {
|
bottomSheet("status") {
|
||||||
StatusSheet(navController = navController, topNav = topNav)
|
StatusSheet(navController = navController, topNav = topNav)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dialog("report/message/{messageId}") { backStackEntry ->
|
||||||
|
val messageId = backStackEntry.arguments?.getString("messageId")
|
||||||
|
if (messageId != null) {
|
||||||
|
ReportMessageDialog(
|
||||||
|
navController = navController,
|
||||||
|
messageId = messageId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,332 @@
|
||||||
|
package chat.revolt.screens.chat.dialogs.safety
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||||
|
import androidx.compose.material.icons.filled.Check
|
||||||
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.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.putMessageReport
|
||||||
|
import chat.revolt.api.routes.user.blockUser
|
||||||
|
import chat.revolt.api.schemas.ContentReportReason
|
||||||
|
import chat.revolt.components.chat.Message
|
||||||
|
import chat.revolt.components.generic.FormTextField
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
enum class ReportingState {
|
||||||
|
Reason,
|
||||||
|
Sending,
|
||||||
|
Done,
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ReportMessageDialog(
|
||||||
|
navController: NavController,
|
||||||
|
messageId: String,
|
||||||
|
) {
|
||||||
|
val message = RevoltAPI.messageCache[messageId]
|
||||||
|
if (message == null) {
|
||||||
|
navController.popBackStack()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val state = remember { mutableStateOf(ReportingState.Reason) }
|
||||||
|
|
||||||
|
val selectedReason = remember { mutableStateOf("Illegal") }
|
||||||
|
val userAddedContext = remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
when (state.value) {
|
||||||
|
ReportingState.Reason -> {
|
||||||
|
val reasons = mapOf(
|
||||||
|
"Illegal" to stringResource(id = R.string.report_reason_content_illegal),
|
||||||
|
"PromotesHarm" to stringResource(id = R.string.report_reason_content_promotes_harm),
|
||||||
|
"SpamAbuse" to stringResource(id = R.string.report_reason_content_spam_abuse),
|
||||||
|
"Malware" to stringResource(id = R.string.report_reason_content_malware),
|
||||||
|
"Harassment" to stringResource(id = R.string.report_reason_content_harassment),
|
||||||
|
"Other" 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),
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Column {
|
||||||
|
Text(text = stringResource(id = R.string.report_message))
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.report_message_preview),
|
||||||
|
fontSize = 12.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(MaterialTheme.shapes.medium)
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp))
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.heightIn(max = 200.dp)
|
||||||
|
.padding(bottom = 8.dp)
|
||||||
|
) {
|
||||||
|
Message(
|
||||||
|
message = message.copy(
|
||||||
|
tail = false,
|
||||||
|
masquerade = null
|
||||||
|
),
|
||||||
|
truncate = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Box {
|
||||||
|
TextField(
|
||||||
|
value = reasons[selectedReason.value]
|
||||||
|
?: stringResource(id = R.string.unknown),
|
||||||
|
onValueChange = {
|
||||||
|
selectedReason.value = it
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.report_reason),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
readOnly = true,
|
||||||
|
trailingIcon = {
|
||||||
|
IconToggleButton(
|
||||||
|
checked = reasonDropdownExpanded.value,
|
||||||
|
onCheckedChange = {
|
||||||
|
reasonDropdownExpanded.value = it
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ArrowDropDown,
|
||||||
|
contentDescription = stringResource(id = R.string.report_reason)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
DropdownMenu(
|
||||||
|
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))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.report_reason_additional_hint),
|
||||||
|
fontSize = 12.sp,
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
|
FormTextField(
|
||||||
|
value = userAddedContext.value,
|
||||||
|
label = stringResource(id = R.string.report_reason_additional),
|
||||||
|
onChange = {
|
||||||
|
userAddedContext.value = it
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = {
|
||||||
|
navController.popBackStack()
|
||||||
|
}) {
|
||||||
|
Text(text = stringResource(id = R.string.report_cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = {
|
||||||
|
state.value = ReportingState.Sending
|
||||||
|
}) {
|
||||||
|
Text(text = stringResource(id = R.string.report_submit))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ReportingState.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("ReportMessageDialog", "Reporting message $messageId")
|
||||||
|
putMessageReport(
|
||||||
|
messageId,
|
||||||
|
ContentReportReason.valueOf(selectedReason.value),
|
||||||
|
userAddedContext.value
|
||||||
|
)
|
||||||
|
state.value = ReportingState.Done
|
||||||
|
} catch (e: Exception) {
|
||||||
|
state.value = ReportingState.Error
|
||||||
|
Log.e("ReportMessageDialog", "Failed to report message", e)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {},
|
||||||
|
confirmButton = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ReportingState.Done -> {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.report_block_question),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = {
|
||||||
|
navController.popBackStack()
|
||||||
|
}) {
|
||||||
|
Text(text = stringResource(id = R.string.report_block_no))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = {
|
||||||
|
scope.launch {
|
||||||
|
blockUser(message.author ?: return@launch)
|
||||||
|
}
|
||||||
|
navController.popBackStack()
|
||||||
|
}) {
|
||||||
|
Text(text = stringResource(id = R.string.report_block_yes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ReportingState.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()
|
||||||
|
}) {
|
||||||
|
Text(text = stringResource(id = R.string.ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,7 +25,6 @@ import chat.revolt.api.RevoltAPI
|
||||||
import chat.revolt.callbacks.UiCallbacks
|
import chat.revolt.callbacks.UiCallbacks
|
||||||
import chat.revolt.components.chat.Message
|
import chat.revolt.components.chat.Message
|
||||||
import chat.revolt.components.generic.SheetClickable
|
import chat.revolt.components.generic.SheetClickable
|
||||||
import chat.revolt.api.schemas.Message as MessageSchema
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MessageContextSheet(
|
fun MessageContextSheet(
|
||||||
|
|
@ -54,7 +53,10 @@ fun MessageContextSheet(
|
||||||
.padding(bottom = 8.dp)
|
.padding(bottom = 8.dp)
|
||||||
) {
|
) {
|
||||||
Message(
|
Message(
|
||||||
message = message.mergeWithPartial(MessageSchema(tail = false)),
|
message = message.copy(
|
||||||
|
tail = false,
|
||||||
|
masquerade = null
|
||||||
|
),
|
||||||
truncate = true
|
truncate = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -264,12 +266,7 @@ fun MessageContextSheet(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Toast.makeText(
|
navController.navigate("report/message/${message.id}")
|
||||||
context,
|
|
||||||
context.getString(R.string.comingsoon_toast),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
navController.popBackStack()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
<string name="back">← Back</string>
|
<string name="back">← Back</string>
|
||||||
<string name="next">Next →</string>
|
<string name="next">Next →</string>
|
||||||
|
<string name="ok">OK</string>
|
||||||
<string name="cancel">Cancel</string>
|
<string name="cancel">Cancel</string>
|
||||||
<string name="lets_go">Let\'s go</string>
|
<string name="lets_go">Let\'s go</string>
|
||||||
<string name="loading">Fetching some info, hang in there…</string>
|
<string name="loading">Fetching some info, hang in there…</string>
|
||||||
|
|
@ -138,6 +139,50 @@
|
||||||
<string name="message_context_sheet_actions_react">React</string>
|
<string name="message_context_sheet_actions_react">React</string>
|
||||||
<string name="message_context_sheet_actions_report">Report</string>
|
<string name="message_context_sheet_actions_report">Report</string>
|
||||||
|
|
||||||
|
<string name="report">Report</string>
|
||||||
|
<string name="report_cancel">Cancel</string>
|
||||||
|
|
||||||
|
<string name="report_message">Thank you for taking the time to report this message. Please provide a reason for reporting this message.</string>
|
||||||
|
<string name="report_message_preview">Selected message:</string>
|
||||||
|
<string name="report_server">Thank you for taking the time to report this server. Please provide a reason for reporting this server.</string>
|
||||||
|
<string name="report_server_preview">Selected server:</string>
|
||||||
|
<string name="report_user">Thank you for taking the time to report this user. Please provide a reason for reporting this user.</string>
|
||||||
|
<string name="report_user_preview">Selected user:</string>
|
||||||
|
|
||||||
|
<string name="report_reason">Reason</string>
|
||||||
|
|
||||||
|
<string name="report_reason_content_illegal">Illegal content</string>
|
||||||
|
<string name="report_reason_content_promotes_harm">Promotes harm</string>
|
||||||
|
<string name="report_reason_content_spam_abuse">Spam or similar platform abuse</string>
|
||||||
|
<string name="report_reason_content_malware">Malware or phishing</string>
|
||||||
|
<string name="report_reason_content_harassment">Harassment or cyberbullying</string>
|
||||||
|
<string name="report_reason_content_other">Other</string>
|
||||||
|
|
||||||
|
<string name="report_reason_user_spam_abuse">Spam or similar platform abuse</string>
|
||||||
|
<string name="report_reason_user_inappropriate_content">Inappropriate content (like NSFW)</string>
|
||||||
|
<string name="report_reason_user_impersonation">Impersonation</string>
|
||||||
|
<string name="report_reason_user_ban_evasion">Ban evasion</string>
|
||||||
|
<string name="report_reason_user_underage">Not of minimum age to use the platform</string>
|
||||||
|
<string name="report_reason_user_other">Other</string>
|
||||||
|
|
||||||
|
<string name="report_reason_additional">Additional information</string>
|
||||||
|
<string name="report_reason_additional_hint">Any additional information that may help us in our investigation. Optional.</string>
|
||||||
|
|
||||||
|
<string name="report_submit">Submit</string>
|
||||||
|
<string name="report_submitting">Reporting…</string>
|
||||||
|
<string name="report_submit_success">Reported</string>
|
||||||
|
|
||||||
|
<string name="report_submit_thanks">Thank you for helping to keep Revolt safe. We will review your report as soon as possible.</string>
|
||||||
|
|
||||||
|
<string name="report_submit_error_header">Error</string>
|
||||||
|
<string name="report_submit_error">An error occurred while submitting your report. Please try again later.</string>
|
||||||
|
|
||||||
|
<string name="report_submit_close">Close</string>
|
||||||
|
|
||||||
|
<string name="report_block_question">Would you like to block this user?</string>
|
||||||
|
<string name="report_block_yes">Block</string>
|
||||||
|
<string name="report_block_no">Don\'t block</string>
|
||||||
|
|
||||||
<string name="settings_appearance">Appearance</string>
|
<string name="settings_appearance">Appearance</string>
|
||||||
<string name="settings_appearance_theme">Theme</string>
|
<string name="settings_appearance_theme">Theme</string>
|
||||||
<string name="settings_appearance_theme_none">System</string>
|
<string name="settings_appearance_theme_none">System</string>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue