import React from 'react' import {Keyboard, StyleProp, View, ViewStyle} from 'react-native' import { AppBskyFeedDefs, AppBskyFeedGetPostThread, AppBskyGraphDefs, AtUri, BskyAgent, } from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' import {createThreadgate} from '#/lib/api' import {until} from '#/lib/async/until' import {HITSLOP_10} from '#/lib/constants' import {makeListLink, makeProfileLink} from '#/lib/routes/links' import {logger} from '#/logger' import {isNative} from '#/platform/detection' import {RQKEY_ROOT as POST_THREAD_RQKEY_ROOT} from '#/state/queries/post-thread' import { ThreadgateSetting, threadgateViewToSettings, } from '#/state/queries/threadgate' import {useAgent} from '#/state/session' import * as Toast from 'view/com/util/Toast' import {atoms as a, useTheme} from '#/alf' import {Button} from '#/components/Button' import * as Dialog from '#/components/Dialog' import {useDialogControl} from '#/components/Dialog' import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign' import {Earth_Stroke2_Corner0_Rounded as Earth} from '#/components/icons/Globe' import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group' import {Text} from '#/components/Typography' import {TextLink} from '../view/com/util/Link' import {ThreadgateEditorDialog} from './dialogs/ThreadgateEditor' import {PencilLine_Stroke2_Corner0_Rounded as PencilLine} from './icons/Pencil' interface WhoCanReplyProps { post: AppBskyFeedDefs.PostView isThreadAuthor: boolean style?: StyleProp } export function WhoCanReply({post, isThreadAuthor, style}: WhoCanReplyProps) { const {_} = useLingui() const t = useTheme() const infoDialogControl = useDialogControl() const editDialogControl = useDialogControl() const agent = useAgent() const queryClient = useQueryClient() const settings = React.useMemo( () => threadgateViewToSettings(post.threadgate), [post], ) const isRootPost = !('reply' in post.record) if (!isRootPost) { return null } if (!settings.length && !isThreadAuthor) { return null } const isEverybody = settings.length === 0 const isNobody = !!settings.find(gate => gate.type === 'nobody') const description = isEverybody ? _(msg`Everybody can reply`) : isNobody ? _(msg`Replies disabled`) : _(msg`Some people can reply`) const onPressEdit = () => { if (isNative && Keyboard.isVisible()) { Keyboard.dismiss() } if (isThreadAuthor) { editDialogControl.open() } else { infoDialogControl.open() } } const onEditConfirm = async (newSettings: ThreadgateSetting[]) => { if (JSON.stringify(settings) === JSON.stringify(newSettings)) { return } try { if (newSettings.length) { await createThreadgate(agent, post.uri, newSettings) } else { await agent.api.com.atproto.repo.deleteRecord({ repo: agent.session!.did, collection: 'app.bsky.feed.threadgate', rkey: new AtUri(post.uri).rkey, }) } await whenAppViewReady(agent, post.uri, res => { const thread = res.data.thread if (AppBskyFeedDefs.isThreadViewPost(thread)) { const fetchedSettings = threadgateViewToSettings( thread.post.threadgate, ) return JSON.stringify(fetchedSettings) === JSON.stringify(newSettings) } return false }) Toast.show(_(msg`Thread settings updated`)) queryClient.invalidateQueries({ queryKey: [POST_THREAD_RQKEY_ROOT], }) } catch (err) { Toast.show( _( msg`There was an issue. Please check your internet connection and try again.`, ), 'xmark', ) logger.error('Failed to edit threadgate', {message: err}) } } return ( <> {isThreadAuthor && ( )} ) } function Icon({ color, width, settings, }: { color: string width?: number settings: ThreadgateSetting[] }) { const isEverybody = settings.length === 0 const isNobody = !!settings.find(gate => gate.type === 'nobody') const IconComponent = isEverybody ? Earth : isNobody ? CircleBanSign : Group return } function WhoCanReplyDialog({ control, post, settings, }: { control: Dialog.DialogControlProps post: AppBskyFeedDefs.PostView settings: ThreadgateSetting[] }) { return ( ) } function WhoCanReplyDialogInner({ post, settings, }: { post: AppBskyFeedDefs.PostView settings: ThreadgateSetting[] }) { const {_} = useLingui() return ( Who can reply? ) } function Rules({ post, settings, }: { post: AppBskyFeedDefs.PostView settings: ThreadgateSetting[] }) { const t = useTheme() return ( {!settings.length ? ( Everybody can reply ) : settings[0].type === 'nobody' ? ( Replies to this thread are disabled ) : ( Only{' '} {settings.map((rule, i) => ( <> ))}{' '} can reply )} ) } function Rule({ rule, post, lists, }: { rule: ThreadgateSetting post: AppBskyFeedDefs.PostView lists: AppBskyGraphDefs.ListViewBasic[] | undefined }) { const t = useTheme() if (rule.type === 'mention') { return mentioned users } if (rule.type === 'following') { return ( users followed by{' '} ) } if (rule.type === 'list') { const list = lists?.find(l => l.uri === rule.list) if (list) { const listUrip = new AtUri(list.uri) return ( {' '} members ) } } } function Separator({i, length}: {i: number; length: number}) { if (length < 2 || i === length - 1) { return null } if (i === length - 2) { return ( <> {length > 2 ? ',' : ''} and{' '} ) } return <>, } async function whenAppViewReady( agent: BskyAgent, uri: string, fn: (res: AppBskyFeedGetPostThread.Response) => boolean, ) { await until( 5, // 5 tries 1e3, // 1s delay between tries fn, () => agent.app.bsky.feed.getPostThread({ uri, depth: 0, }), ) }