handle moderation lock override in group chats
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ import {Pressable, View} from 'react-native'
|
|||||||
import {
|
import {
|
||||||
ChatBskyActorDefs,
|
ChatBskyActorDefs,
|
||||||
ChatBskyConvoDefs,
|
ChatBskyConvoDefs,
|
||||||
|
ChatBskyConvoUnlockConvo,
|
||||||
type ModerationOpts,
|
type ModerationOpts,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {Trans, useLingui} from '@lingui/react/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
@@ -417,6 +418,12 @@ function SettingsHeader({
|
|||||||
if (lock) {
|
if (lock) {
|
||||||
logger.error('Failed to lock group chat', {message: e})
|
logger.error('Failed to lock group chat', {message: e})
|
||||||
Toast.show(l`Failed to lock group chat`, {type: 'error'})
|
Toast.show(l`Failed to lock group chat`, {type: 'error'})
|
||||||
|
} else if (
|
||||||
|
e instanceof ChatBskyConvoUnlockConvo.ConvoLockedByModerationError
|
||||||
|
) {
|
||||||
|
Toast.show(l`This chat is locked by a moderation action`, {
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
logger.error('Failed to unlock group chat', {message: e})
|
logger.error('Failed to unlock group chat', {message: e})
|
||||||
Toast.show(l`Failed to unlock group chat`, {type: 'error'})
|
Toast.show(l`Failed to unlock group chat`, {type: 'error'})
|
||||||
@@ -447,7 +454,11 @@ function SettingsHeader({
|
|||||||
|
|
||||||
const createdAt = new Date(convo.details.createdAt)
|
const createdAt = new Date(convo.details.createdAt)
|
||||||
|
|
||||||
const canLockGroupChat = isOwner && lockStatus !== 'locked-permanently'
|
// A lock forced by a moderation action cannot be undone by the owner.
|
||||||
|
const canLockGroupChat =
|
||||||
|
isOwner &&
|
||||||
|
lockStatus !== 'locked-permanently' &&
|
||||||
|
!convo.details.lockStatusModerationOverride
|
||||||
|
|
||||||
const groupNameComponent = (
|
const groupNameComponent = (
|
||||||
<Text
|
<Text
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export function ChatFooter({
|
|||||||
style={[
|
style={[
|
||||||
a.flex_row,
|
a.flex_row,
|
||||||
a.align_center,
|
a.align_center,
|
||||||
|
a.flex_1,
|
||||||
{
|
{
|
||||||
minHeight: 32,
|
minHeight: 32,
|
||||||
},
|
},
|
||||||
@@ -40,7 +41,7 @@ export function ChatFooter({
|
|||||||
fill={t.atoms.text_contrast_medium.color}
|
fill={t.atoms.text_contrast_medium.color}
|
||||||
style={[a.mr_sm]}
|
style={[a.mr_sm]}
|
||||||
/>
|
/>
|
||||||
<View>
|
<View style={[a.flex_1]}>
|
||||||
<Text
|
<Text
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
style={[
|
style={[
|
||||||
@@ -52,7 +53,7 @@ export function ChatFooter({
|
|||||||
</Text>
|
</Text>
|
||||||
{subheading ? (
|
{subheading ? (
|
||||||
<Text
|
<Text
|
||||||
numberOfLines={1}
|
numberOfLines={2}
|
||||||
style={[
|
style={[
|
||||||
a.text_xs,
|
a.text_xs,
|
||||||
a.leading_snug,
|
a.leading_snug,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {Pressable} from 'react-native'
|
import {Pressable} from 'react-native'
|
||||||
|
import {ChatBskyConvoUnlockConvo} from '@atproto/api'
|
||||||
import {Trans, useLingui} from '@lingui/react/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
import {StackActions, useNavigation} from '@react-navigation/native'
|
import {StackActions, useNavigation} from '@react-navigation/native'
|
||||||
|
|
||||||
@@ -32,12 +33,20 @@ export function ChatLocked({
|
|||||||
|
|
||||||
const primaryMember = convo?.primaryMember
|
const primaryMember = convo?.primaryMember
|
||||||
const isOwner = !!primaryMember && primaryMember.did === currentAccount?.did
|
const isOwner = !!primaryMember && primaryMember.did === currentAccount?.did
|
||||||
|
// The lock is forced by a moderation action, so the owner cannot undo it.
|
||||||
|
const isModerationLock = convo.details.lockStatusModerationOverride
|
||||||
|
|
||||||
const {mutate: lockConvo} = useLockConvo(convo.view.id, {
|
const {mutate: lockConvo} = useLockConvo(convo.view.id, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
Toast.show(l({message: 'Group chat unlocked', context: 'toast'}))
|
Toast.show(l({message: 'Group chat unlocked', context: 'toast'}))
|
||||||
},
|
},
|
||||||
onError: e => {
|
onError: e => {
|
||||||
|
if (e instanceof ChatBskyConvoUnlockConvo.ConvoLockedByModerationError) {
|
||||||
|
Toast.show(l`This chat is locked by a moderation action`, {
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
logger.error('Failed to unlock group chat', {message: e})
|
logger.error('Failed to unlock group chat', {message: e})
|
||||||
Toast.show(l`Failed to unlock group chat`, {type: 'error'})
|
Toast.show(l`Failed to unlock group chat`, {type: 'error'})
|
||||||
},
|
},
|
||||||
@@ -59,10 +68,14 @@ export function ChatLocked({
|
|||||||
return (
|
return (
|
||||||
<ChatFooter
|
<ChatFooter
|
||||||
heading={l`This chat is locked`}
|
heading={l`This chat is locked`}
|
||||||
subheading={l`No one can send messages`}
|
subheading={
|
||||||
|
isModerationLock
|
||||||
|
? l`This group is locked by a moderation action on the owner`
|
||||||
|
: l`No one can send messages`
|
||||||
|
}
|
||||||
icon={LockIcon}>
|
icon={LockIcon}>
|
||||||
{isOwner ? (
|
{isOwner ? (
|
||||||
<>
|
isModerationLock ? null : (
|
||||||
<Pressable
|
<Pressable
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
hitSlop={HITSLOP_10}
|
hitSlop={HITSLOP_10}
|
||||||
@@ -79,7 +92,7 @@ export function ChatLocked({
|
|||||||
<Trans>Unlock chat</Trans>
|
<Trans>Unlock chat</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</>
|
)
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Pressable
|
<Pressable
|
||||||
|
|||||||
@@ -1087,7 +1087,10 @@ export class Convo {
|
|||||||
this.commit()
|
this.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLockStatus(lockStatus: ChatBskyConvoDefs.ConvoLockStatus) {
|
updateLockStatus(
|
||||||
|
lockStatus: ChatBskyConvoDefs.ConvoLockStatus,
|
||||||
|
lockStatusModerationOverride: boolean,
|
||||||
|
) {
|
||||||
if (this.convo?.kind !== 'group') {
|
if (this.convo?.kind !== 'group') {
|
||||||
throw new Error('updateLockStatus can only be called on group convo')
|
throw new Error('updateLockStatus can only be called on group convo')
|
||||||
}
|
}
|
||||||
@@ -1096,6 +1099,7 @@ export class Convo {
|
|||||||
kind: {
|
kind: {
|
||||||
...this.convo.details,
|
...this.convo.details,
|
||||||
lockStatus,
|
lockStatus,
|
||||||
|
lockStatusModerationOverride,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -151,8 +151,15 @@ export function ConvoProvider({
|
|||||||
if (data.kind.joinLink !== convo.convo.details.joinLink) {
|
if (data.kind.joinLink !== convo.convo.details.joinLink) {
|
||||||
convo.updateJoinLink(data.kind.joinLink)
|
convo.updateJoinLink(data.kind.joinLink)
|
||||||
}
|
}
|
||||||
if (data.kind.lockStatus !== convo.convo.details.lockStatus) {
|
if (
|
||||||
convo.updateLockStatus(data.kind.lockStatus)
|
data.kind.lockStatus !== convo.convo.details.lockStatus ||
|
||||||
|
data.kind.lockStatusModerationOverride !==
|
||||||
|
convo.convo.details.lockStatusModerationOverride
|
||||||
|
) {
|
||||||
|
convo.updateLockStatus(
|
||||||
|
data.kind.lockStatus,
|
||||||
|
data.kind.lockStatusModerationOverride,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -532,12 +532,22 @@ export function ListConvosProviderInner({
|
|||||||
}
|
}
|
||||||
return {...convo, rev: log.rev}
|
return {...convo, rev: log.rev}
|
||||||
})
|
})
|
||||||
|
// The log event doesn't say whether the lock is forced by a
|
||||||
|
// moderation override, so refetch to pick up the flag.
|
||||||
|
void queryClient.invalidateQueries({
|
||||||
|
queryKey: CONVO_KEY(log.convoId),
|
||||||
|
})
|
||||||
} else if (ChatBskyConvoDefs.isLogUnlockConvo(log)) {
|
} else if (ChatBskyConvoDefs.isLogUnlockConvo(log)) {
|
||||||
mutateConvoView(log.convoId, convo => {
|
mutateConvoView(log.convoId, convo => {
|
||||||
if (ChatBskyConvoDefs.isGroupConvo(convo.kind)) {
|
if (ChatBskyConvoDefs.isGroupConvo(convo.kind)) {
|
||||||
return {
|
return {
|
||||||
...convo,
|
...convo,
|
||||||
kind: {...convo.kind, lockStatus: 'unlocked'},
|
kind: {
|
||||||
|
...convo.kind,
|
||||||
|
lockStatus: 'unlocked',
|
||||||
|
// An unlocked convo cannot be moderation-locked.
|
||||||
|
lockStatusModerationOverride: false,
|
||||||
|
},
|
||||||
rev: log.rev,
|
rev: log.rev,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user