feat: handling for platform mod
Signed-off-by: Infi <wingit@geist.ga>
This commit is contained in:
parent
38406388b8
commit
b26b8bad47
|
|
@ -0,0 +1,34 @@
|
|||
package chat.revolt.api.internals
|
||||
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.api.internals.SpecialUsers.PLATFORM_MODERATION_USER
|
||||
import chat.revolt.api.schemas.Channel
|
||||
import chat.revolt.api.schemas.ChannelType
|
||||
|
||||
object DirectMessages {
|
||||
fun unreadDMs(): List<Channel> {
|
||||
return RevoltAPI.channelCache.values
|
||||
.filter {
|
||||
it.channelType in listOf(
|
||||
ChannelType.DirectMessage, ChannelType.Group
|
||||
) && it.active == true && it.lastMessageID != null
|
||||
}
|
||||
.filter {
|
||||
it.id?.let { id -> RevoltAPI.unreads.hasUnread(id, it.lastMessageID!!) } ?: false
|
||||
}
|
||||
}
|
||||
|
||||
fun hasPlatformModerationDM(): Boolean {
|
||||
return unreadDMs().any {
|
||||
it.channelType == ChannelType.DirectMessage &&
|
||||
it.recipients?.contains(PLATFORM_MODERATION_USER) ?: false
|
||||
}
|
||||
}
|
||||
|
||||
fun getPlatformModerationDM(): Channel? {
|
||||
return unreadDMs().firstOrNull {
|
||||
it.channelType == ChannelType.DirectMessage &&
|
||||
it.recipients?.contains(PLATFORM_MODERATION_USER) ?: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package chat.revolt.api.internals
|
||||
|
||||
object SpecialUsers {
|
||||
val PLATFORM_MODERATION_USER = "01FC17E1WTM2BGE4F3ARN3FDAF"
|
||||
|
||||
val TRUSTED_MODERATION_BOTS = listOf(
|
||||
"01GXBYCNQ52A9QYCQ99RBPXPAW", // AutoMod
|
||||
"01FCXRNNVW69AMSHBE61W1M5T3", // AutoMod Nightly
|
||||
)
|
||||
}
|
||||
|
|
@ -61,6 +61,8 @@ fun MessageField(
|
|||
forceSendButton: Boolean = false,
|
||||
disabled: Boolean = false,
|
||||
editMode: Boolean = false,
|
||||
denied: Boolean = false,
|
||||
denyReason: String? = null,
|
||||
cancelEdit: () -> Unit = {},
|
||||
onFocusChange: (Boolean) -> Unit = {},
|
||||
) {
|
||||
|
|
@ -73,7 +75,7 @@ fun MessageField(
|
|||
ChannelType.SavedMessages -> R.string.message_field_placeholder_notes
|
||||
}
|
||||
|
||||
val sendButtonVisible = (messageContent.isNotBlank() || forceSendButton) && !disabled
|
||||
val sendButtonVisible = (messageContent.isNotBlank() || forceSendButton) && !disabled && !denied
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
|
|
@ -84,7 +86,7 @@ fun MessageField(
|
|||
value = messageContent,
|
||||
onValueChange = onMessageContentChange,
|
||||
singleLine = false,
|
||||
enabled = !disabled,
|
||||
enabled = !disabled && !denied,
|
||||
textStyle = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.onSurface),
|
||||
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
|
||||
modifier = modifier
|
||||
|
|
@ -104,14 +106,22 @@ fun MessageField(
|
|||
visualTransformation = VisualTransformation.None,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = placeholderResource,
|
||||
channelName
|
||||
),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
if (denied) {
|
||||
Text(
|
||||
text = denyReason
|
||||
?: stringResource(R.string.message_field_denied_generic),
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = placeholderResource,
|
||||
channelName
|
||||
),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = TextFieldDefaults.colors(
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
|
|
@ -127,28 +137,30 @@ fun MessageField(
|
|||
),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
when {
|
||||
editMode -> Icons.Default.Close
|
||||
else -> Icons.Default.Add
|
||||
},
|
||||
tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f),
|
||||
contentDescription = stringResource(id = R.string.add_attachment_alt),
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.size(32.dp)
|
||||
.clickable {
|
||||
when {
|
||||
editMode -> cancelEdit()
|
||||
else -> {
|
||||
focusRequester.freeFocus() // hide keyboard because it's annoying
|
||||
onAddAttachment()
|
||||
if (!denied) {
|
||||
Icon(
|
||||
when {
|
||||
editMode -> Icons.Default.Close
|
||||
else -> Icons.Default.Add
|
||||
},
|
||||
tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f),
|
||||
contentDescription = stringResource(id = R.string.add_attachment_alt),
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.size(32.dp)
|
||||
.clickable {
|
||||
when {
|
||||
editMode -> cancelEdit()
|
||||
else -> {
|
||||
focusRequester.freeFocus() // hide keyboard because it's annoying
|
||||
onAddAttachment()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(4.dp)
|
||||
.testTag("add_attachment")
|
||||
)
|
||||
.padding(4.dp)
|
||||
.testTag("add_attachment")
|
||||
)
|
||||
}
|
||||
},
|
||||
trailingIcon = {
|
||||
AnimatedVisibility(sendButtonVisible,
|
||||
|
|
|
|||
|
|
@ -52,12 +52,16 @@ import androidx.navigation.compose.dialog
|
|||
import androidx.navigation.compose.rememberNavController
|
||||
import chat.revolt.R
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.api.internals.ChannelUtils
|
||||
import chat.revolt.api.internals.DirectMessages
|
||||
import chat.revolt.api.realtime.DisconnectionState
|
||||
import chat.revolt.api.realtime.RealtimeSocket
|
||||
import chat.revolt.api.routes.server.fetchMembers
|
||||
import chat.revolt.api.schemas.ChannelType
|
||||
import chat.revolt.api.schemas.User
|
||||
import chat.revolt.api.settings.SyncedSettings
|
||||
import chat.revolt.components.chat.DisconnectedNotice
|
||||
import chat.revolt.components.generic.GroupIcon
|
||||
import chat.revolt.components.generic.UserAvatar
|
||||
import chat.revolt.components.generic.presenceFromStatus
|
||||
import chat.revolt.components.screens.chat.drawer.channel.ChannelList
|
||||
|
|
@ -190,6 +194,8 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hil
|
|||
composition = sidebarSparkComposition,
|
||||
)
|
||||
|
||||
var showPlatformModDMHint by remember { mutableStateOf(false) }
|
||||
|
||||
var showStatusSheet by remember { mutableStateOf(false) }
|
||||
var showAddServerSheet by remember { mutableStateOf(false) }
|
||||
|
||||
|
|
@ -254,6 +260,16 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hil
|
|||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(DirectMessages.unreadDMs()) {
|
||||
snapshotFlow { DirectMessages.unreadDMs() }
|
||||
.distinctUntilChanged()
|
||||
.collect { _ ->
|
||||
if (DirectMessages.hasPlatformModerationDM()) {
|
||||
showPlatformModDMHint = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showSidebarSpark.value) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {},
|
||||
|
|
@ -287,6 +303,29 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hil
|
|||
)
|
||||
}
|
||||
|
||||
if (showPlatformModDMHint) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {},
|
||||
title = {
|
||||
Text(stringResource(id = R.string.notice_platform_mod_dm_title))
|
||||
},
|
||||
text = {
|
||||
Text(stringResource(id = R.string.notice_platform_mod_dm_description))
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
showPlatformModDMHint = false
|
||||
DirectMessages.getPlatformModerationDM()?.id?.let {
|
||||
viewModel.navigateToServer("home", navController)
|
||||
viewModel.navigateToChannel(it, navController)
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(id = R.string.notice_platform_mod_dm_acknowledge))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (showStatusSheet) {
|
||||
val statusSheetState = rememberModalBottomSheetState()
|
||||
|
||||
|
|
@ -409,6 +448,68 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hil
|
|||
.size(48.dp)
|
||||
)
|
||||
|
||||
DirectMessages.unreadDMs().forEach {
|
||||
when (it.channelType) {
|
||||
ChannelType.Group -> GroupIcon(
|
||||
name = it.name ?: "?",
|
||||
size = 48.dp,
|
||||
onClick = {
|
||||
it.id?.let { id ->
|
||||
viewModel.navigateToServer(
|
||||
"home",
|
||||
navController
|
||||
)
|
||||
viewModel.navigateToChannel(
|
||||
id,
|
||||
navController
|
||||
)
|
||||
}
|
||||
},
|
||||
icon = it.icon,
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.size(48.dp)
|
||||
)
|
||||
|
||||
else -> {
|
||||
val partner =
|
||||
if (it.channelType == ChannelType.DirectMessage) RevoltAPI.userCache[ChannelUtils.resolveDMPartner(
|
||||
it
|
||||
)] else null
|
||||
|
||||
UserAvatar(
|
||||
username = partner?.let { p ->
|
||||
User.resolveDefaultName(
|
||||
p
|
||||
)
|
||||
} ?: it.name ?: "?",
|
||||
presence = presenceFromStatus(
|
||||
partner?.status?.presence ?: ""
|
||||
),
|
||||
userId = partner?.id ?: it.id ?: "",
|
||||
avatar = partner?.avatar ?: it.icon,
|
||||
size = 48.dp,
|
||||
presenceSize = 16.dp,
|
||||
onClick = {
|
||||
it.id?.let { id ->
|
||||
viewModel.navigateToServer(
|
||||
"home",
|
||||
navController
|
||||
)
|
||||
viewModel.navigateToChannel(
|
||||
id,
|
||||
navController
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.size(48.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ServerDrawerSeparator()
|
||||
|
||||
// This seems to confuse the formatter, here's what it does:
|
||||
|
|
|
|||
|
|
@ -470,6 +470,8 @@ fun ChannelScreen(
|
|||
),
|
||||
forceSendButton = viewModel.pendingAttachments.isNotEmpty(),
|
||||
disabled = viewModel.pendingAttachments.isNotEmpty() && viewModel.isSendingMessage,
|
||||
denied = viewModel.denyMessageField,
|
||||
denyReason = stringResource(id = viewModel.denyMessageFieldReasonResource),
|
||||
editMode = viewModel.editingMessage != null,
|
||||
cancelEdit = viewModel::cancelEditingMessage,
|
||||
onFocusChange = { nowFocused ->
|
||||
|
|
|
|||
|
|
@ -3,14 +3,18 @@ package chat.revolt.screens.chat.views.channel
|
|||
import android.util.Log
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.toMutableStateList
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import chat.revolt.R
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.api.RevoltJson
|
||||
import chat.revolt.api.internals.ChannelUtils
|
||||
import chat.revolt.api.internals.SpecialUsers
|
||||
import chat.revolt.api.internals.ULID
|
||||
import chat.revolt.api.realtime.RealtimeSocketFrames
|
||||
import chat.revolt.api.realtime.frames.receivable.ChannelStartTypingFrame
|
||||
|
|
@ -66,6 +70,9 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
|
||||
var editingMessage by mutableStateOf<String?>(null)
|
||||
|
||||
var denyMessageField by mutableStateOf(false)
|
||||
var denyMessageFieldReasonResource by mutableIntStateOf(R.string.message_field_denied_generic)
|
||||
|
||||
private fun popAttachmentBatch() {
|
||||
pendingAttachments =
|
||||
pendingAttachments.drop(MAX_ATTACHMENTS_PER_MESSAGE).toMutableStateList()
|
||||
|
|
@ -152,6 +159,8 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
activeChannel = RevoltAPI.channelCache[id]
|
||||
}
|
||||
|
||||
checkShouldDenyMessageField()
|
||||
|
||||
if (activeChannel?.lastMessageID != null) {
|
||||
ackNewest()
|
||||
} else {
|
||||
|
|
@ -409,4 +418,25 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
editingMessage = null
|
||||
pendingMessageContent = ""
|
||||
}
|
||||
|
||||
private fun checkShouldDenyMessageField() {
|
||||
// TODO Check for send message permission.
|
||||
val hasPermission = true
|
||||
|
||||
if (activeChannel == null) return
|
||||
|
||||
val partnerId = ChannelUtils.resolveDMPartner(activeChannel!!) ?: return
|
||||
|
||||
denyMessageField = when {
|
||||
partnerId == SpecialUsers.PLATFORM_MODERATION_USER -> true
|
||||
!hasPermission -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
denyMessageFieldReasonResource = when {
|
||||
partnerId == SpecialUsers.PLATFORM_MODERATION_USER -> R.string.message_field_denied_platform_moderation
|
||||
!hasPermission -> R.string.message_field_denied_no_permission
|
||||
else -> R.string.message_field_denied_generic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,6 +134,12 @@
|
|||
<string name="message_field_placeholder_group">Message %1$s</string>
|
||||
<string name="message_field_placeholder_notes">Add a note</string>
|
||||
|
||||
<string name="message_field_decoration_trusted_moderation_bot">Verified moderation bot</string>
|
||||
|
||||
<string name="message_field_denied_generic">You can\'t send messages in this channel.</string>
|
||||
<string name="message_field_denied_no_permission">You don\'t have permission to send messages in this channel.</string>
|
||||
<string name="message_field_denied_platform_moderation">This is a trusted channel for notices from our moderation team. You can\'t send messages here.</string>
|
||||
|
||||
<string name="reply_message_not_cached">Unknown message, tap to jump</string>
|
||||
<string name="reply_message_empty_has_attachments">Sent attachments</string>
|
||||
|
||||
|
|
@ -297,6 +303,10 @@
|
|||
<string name="spark_sidebar_settings_tutorial_description_2">Then long tap your profile picture to open the settings.</string>
|
||||
<string name="spark_sidebar_settings_tutorial_acknowledge">Got it</string>
|
||||
|
||||
<string name="notice_platform_mod_dm_title">Important notice regarding your account</string>
|
||||
<string name="notice_platform_mod_dm_description">You have received an important notice regarding your account from our moderation team. Please read it carefully.</string>
|
||||
<string name="notice_platform_mod_dm_acknowledge">View</string>
|
||||
|
||||
<string name="settings_category_general">General</string>
|
||||
<string name="settings_category_miscellaneous">Miscellaneous</string>
|
||||
<string name="settings_category_last" translatable="false">Revolt v%1$s</string>
|
||||
|
|
|
|||
Loading…
Reference in New Issue