Compare commits

...

2 Commits

Author SHA1 Message Date
Samuel Newman 0c3def2033 handle moderation lock override in group chats
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 21:51:21 +03:00
Samuel Newman 560e3fb58a bump @atproto/api to 0.20.12
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 21:51:21 +03:00
8 changed files with 62 additions and 16 deletions
+1 -1
View File
@@ -93,7 +93,7 @@
"prettier": "prettier --check ."
},
"dependencies": {
"@atproto/api": "0.20.11",
"@atproto/api": "0.20.12",
"@atproto/syntax": "0.6.1",
"@bitdrift/react-native": "^0.6.8",
"@braintree/sanitize-url": "^6.0.2",
+5 -5
View File
@@ -242,8 +242,8 @@ importers:
.:
dependencies:
'@atproto/api':
specifier: 0.20.11
version: 0.20.11
specifier: 0.20.12
version: 0.20.12
'@atproto/syntax':
specifier: 0.6.1
version: 0.6.1
@@ -877,8 +877,8 @@ packages:
graphql:
optional: true
'@atproto/api@0.20.11':
resolution: {integrity: sha512-1NoVJpBDAdotxo1iMZdMd75JstpdKWgBYOnxfVD4m+52bRjgU4cFg3EOGNUognZntFgL/bIHyEgyN7SJWVf6Ig==}
'@atproto/api@0.20.12':
resolution: {integrity: sha512-pNCrl/BSmkjlrVu0W5A9zkOVRIWdWAdajuHX/EyhDZFxu8HExsiQi6j46H5kv85GrNRXS/QtULE+ocSuMtEJfw==}
engines: {node: '>=22'}
'@atproto/common-web@0.5.0':
@@ -9493,7 +9493,7 @@ snapshots:
'@0no-co/graphql.web@1.2.0': {}
'@atproto/api@0.20.11':
'@atproto/api@0.20.12':
dependencies:
'@atproto/common-web': 0.5.0
'@atproto/lexicon': 0.7.1
@@ -3,6 +3,7 @@ import {Pressable, View} from 'react-native'
import {
ChatBskyActorDefs,
ChatBskyConvoDefs,
ChatBskyConvoUnlockConvo,
type ModerationOpts,
} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro'
@@ -417,6 +418,12 @@ function SettingsHeader({
if (lock) {
logger.error('Failed to lock group chat', {message: e})
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 {
logger.error('Failed to unlock group chat', {message: e})
Toast.show(l`Failed to unlock group chat`, {type: 'error'})
@@ -447,7 +454,11 @@ function SettingsHeader({
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 = (
<Text
@@ -31,6 +31,7 @@ export function ChatFooter({
style={[
a.flex_row,
a.align_center,
a.flex_1,
{
minHeight: 32,
},
@@ -40,7 +41,7 @@ export function ChatFooter({
fill={t.atoms.text_contrast_medium.color}
style={[a.mr_sm]}
/>
<View>
<View style={[a.flex_1]}>
<Text
numberOfLines={1}
style={[
@@ -52,7 +53,7 @@ export function ChatFooter({
</Text>
{subheading ? (
<Text
numberOfLines={1}
numberOfLines={2}
style={[
a.text_xs,
a.leading_snug,
+16 -3
View File
@@ -1,4 +1,5 @@
import {Pressable} from 'react-native'
import {ChatBskyConvoUnlockConvo} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro'
import {StackActions, useNavigation} from '@react-navigation/native'
@@ -32,12 +33,20 @@ export function ChatLocked({
const primaryMember = convo?.primaryMember
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, {
onSuccess: () => {
Toast.show(l({message: 'Group chat unlocked', context: 'toast'}))
},
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})
Toast.show(l`Failed to unlock group chat`, {type: 'error'})
},
@@ -59,10 +68,14 @@ export function ChatLocked({
return (
<ChatFooter
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}>
{isOwner ? (
<>
isModerationLock ? null : (
<Pressable
accessibilityRole="button"
hitSlop={HITSLOP_10}
@@ -79,7 +92,7 @@ export function ChatLocked({
<Trans>Unlock chat</Trans>
</Text>
</Pressable>
</>
)
) : (
<>
<Pressable
+5 -1
View File
@@ -1087,7 +1087,10 @@ export class Convo {
this.commit()
}
updateLockStatus(lockStatus: ChatBskyConvoDefs.ConvoLockStatus) {
updateLockStatus(
lockStatus: ChatBskyConvoDefs.ConvoLockStatus,
lockStatusModerationOverride: boolean,
) {
if (this.convo?.kind !== 'group') {
throw new Error('updateLockStatus can only be called on group convo')
}
@@ -1096,6 +1099,7 @@ export class Convo {
kind: {
...this.convo.details,
lockStatus,
lockStatusModerationOverride,
},
})
+9 -2
View File
@@ -151,8 +151,15 @@ export function ConvoProvider({
if (data.kind.joinLink !== convo.convo.details.joinLink) {
convo.updateJoinLink(data.kind.joinLink)
}
if (data.kind.lockStatus !== convo.convo.details.lockStatus) {
convo.updateLockStatus(data.kind.lockStatus)
if (
data.kind.lockStatus !== convo.convo.details.lockStatus ||
data.kind.lockStatusModerationOverride !==
convo.convo.details.lockStatusModerationOverride
) {
convo.updateLockStatus(
data.kind.lockStatus,
data.kind.lockStatusModerationOverride,
)
}
}
if (
@@ -532,12 +532,22 @@ export function ListConvosProviderInner({
}
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)) {
mutateConvoView(log.convoId, convo => {
if (ChatBskyConvoDefs.isGroupConvo(convo.kind)) {
return {
...convo,
kind: {...convo.kind, lockStatus: 'unlocked'},
kind: {
...convo.kind,
lockStatus: 'unlocked',
// An unlocked convo cannot be moderation-locked.
lockStatusModerationOverride: false,
},
rev: log.rev,
}
}