[Chat] Handle moderation lock override in group chats (#10835)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -93,7 +93,7 @@
|
|||||||
"prettier": "prettier --check ."
|
"prettier": "prettier --check ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atproto/api": "0.20.11",
|
"@atproto/api": "0.20.12",
|
||||||
"@atproto/syntax": "0.6.1",
|
"@atproto/syntax": "0.6.1",
|
||||||
"@bitdrift/react-native": "^0.6.8",
|
"@bitdrift/react-native": "^0.6.8",
|
||||||
"@braintree/sanitize-url": "^6.0.2",
|
"@braintree/sanitize-url": "^6.0.2",
|
||||||
|
|||||||
Generated
+5
-5
@@ -242,8 +242,8 @@ importers:
|
|||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@atproto/api':
|
'@atproto/api':
|
||||||
specifier: 0.20.11
|
specifier: 0.20.12
|
||||||
version: 0.20.11
|
version: 0.20.12
|
||||||
'@atproto/syntax':
|
'@atproto/syntax':
|
||||||
specifier: 0.6.1
|
specifier: 0.6.1
|
||||||
version: 0.6.1
|
version: 0.6.1
|
||||||
@@ -877,8 +877,8 @@ packages:
|
|||||||
graphql:
|
graphql:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@atproto/api@0.20.11':
|
'@atproto/api@0.20.12':
|
||||||
resolution: {integrity: sha512-1NoVJpBDAdotxo1iMZdMd75JstpdKWgBYOnxfVD4m+52bRjgU4cFg3EOGNUognZntFgL/bIHyEgyN7SJWVf6Ig==}
|
resolution: {integrity: sha512-pNCrl/BSmkjlrVu0W5A9zkOVRIWdWAdajuHX/EyhDZFxu8HExsiQi6j46H5kv85GrNRXS/QtULE+ocSuMtEJfw==}
|
||||||
engines: {node: '>=22'}
|
engines: {node: '>=22'}
|
||||||
|
|
||||||
'@atproto/common-web@0.5.0':
|
'@atproto/common-web@0.5.0':
|
||||||
@@ -9493,7 +9493,7 @@ snapshots:
|
|||||||
|
|
||||||
'@0no-co/graphql.web@1.2.0': {}
|
'@0no-co/graphql.web@1.2.0': {}
|
||||||
|
|
||||||
'@atproto/api@0.20.11':
|
'@atproto/api@0.20.12':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@atproto/common-web': 0.5.0
|
'@atproto/common-web': 0.5.0
|
||||||
'@atproto/lexicon': 0.7.1
|
'@atproto/lexicon': 0.7.1
|
||||||
|
|||||||
@@ -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