Bluesky app fork with some witchin' additions 馃挮
witchsky.app
bluesky
fork
client
1import {useEffect, useMemo, useState} from 'react'
2import {Keyboard, type StyleProp, type ViewStyle} from 'react-native'
3import {type AnimatedStyle} from 'react-native-reanimated'
4import {type AppBskyFeedPostgate} from '@atproto/api'
5import {msg} from '@lingui/core/macro'
6import {useLingui} from '@lingui/react'
7import {Trans} from '@lingui/react/macro'
8import deepEqual from 'fast-deep-equal'
9
10import {isNetworkError} from '#/lib/strings/errors'
11import {logger} from '#/logger'
12import {usePostInteractionSettingsMutation} from '#/state/queries/post-interaction-settings'
13import {createPostgateRecord} from '#/state/queries/postgate/util'
14import {usePreferencesQuery} from '#/state/queries/preferences'
15import {
16 type ThreadgateAllowUISetting,
17 threadgateAllowUISettingToAllowRecordValue,
18 threadgateRecordToAllowUISetting,
19} from '#/state/queries/threadgate'
20import {Button, ButtonIcon, ButtonText} from '#/components/Button'
21import * as Dialog from '#/components/Dialog'
22import {PostInteractionSettingsControlledDialog} from '#/components/dialogs/PostInteractionSettingsDialog'
23import {TinyChevronBottom_Stroke2_Corner0_Rounded as TinyChevronIcon} from '#/components/icons/Chevron'
24import {Earth_Stroke2_Corner0_Rounded as EarthIcon} from '#/components/icons/Globe'
25import {Group3_Stroke2_Corner0_Rounded as GroupIcon} from '#/components/icons/Group'
26import * as Tooltip from '#/components/Tooltip'
27import {Text} from '#/components/Typography'
28import {useAnalytics} from '#/analytics'
29import {IS_NATIVE} from '#/env'
30import {useThreadgateNudged} from '#/storage/hooks/threadgate-nudged'
31
32export function ThreadgateBtn({
33 postgate,
34 onChangePostgate,
35 threadgateAllowUISettings,
36 onChangeThreadgateAllowUISettings,
37}: {
38 postgate: AppBskyFeedPostgate.Record
39 onChangePostgate: (v: AppBskyFeedPostgate.Record) => void
40
41 threadgateAllowUISettings: ThreadgateAllowUISetting[]
42 onChangeThreadgateAllowUISettings: (v: ThreadgateAllowUISetting[]) => void
43
44 style?: StyleProp<AnimatedStyle<ViewStyle>>
45}) {
46 const {_} = useLingui()
47 const ax = useAnalytics()
48 const control = Dialog.useDialogControl()
49 const [threadgateNudged, setThreadgateNudged] = useThreadgateNudged()
50 const [showTooltip, setShowTooltip] = useState(false)
51 const [tooltipWasShown] = useState(!threadgateNudged)
52
53 useEffect(() => {
54 if (!threadgateNudged) {
55 const timeout = setTimeout(() => {
56 setShowTooltip(true)
57 }, 1000)
58 return () => clearTimeout(timeout)
59 }
60 }, [threadgateNudged])
61
62 const onDismissTooltip = (visible: boolean) => {
63 if (visible) return
64 setThreadgateNudged(true)
65 setShowTooltip(false)
66 }
67
68 const {data: preferences} = usePreferencesQuery()
69 const [persist, setPersist] = useState(false)
70
71 const onPress = () => {
72 ax.metric('composer:threadgate:open', {
73 nudged: tooltipWasShown,
74 })
75
76 if (IS_NATIVE && Keyboard.isVisible()) {
77 Keyboard.dismiss()
78 }
79
80 setShowTooltip(false)
81 setThreadgateNudged(true)
82
83 control.open()
84 }
85
86 const prefThreadgateAllowUISettings = threadgateRecordToAllowUISetting({
87 $type: 'app.bsky.feed.threadgate',
88 post: '',
89 createdAt: new Date().toISOString(),
90 allow: preferences?.postInteractionSettings.threadgateAllowRules,
91 })
92 const prefPostgate = createPostgateRecord({
93 post: '',
94 embeddingRules:
95 preferences?.postInteractionSettings?.postgateEmbeddingRules || [],
96 })
97
98 const isDirty = useMemo(() => {
99 const everybody = [{type: 'everybody'}]
100 return (
101 !deepEqual(
102 threadgateAllowUISettings,
103 prefThreadgateAllowUISettings ?? everybody,
104 ) ||
105 !deepEqual(postgate.embeddingRules, prefPostgate?.embeddingRules ?? [])
106 )
107 }, [
108 prefThreadgateAllowUISettings,
109 prefPostgate,
110 threadgateAllowUISettings,
111 postgate,
112 ])
113
114 const {mutate: persistChanges, isPending: isSaving} =
115 usePostInteractionSettingsMutation({
116 onError: err => {
117 if (!isNetworkError(err)) {
118 logger.error('Failed to persist threadgate settings', {
119 safeMessage: err,
120 })
121 }
122 },
123 onSettled: () => {
124 control.close(() => {
125 setPersist(false)
126 })
127 },
128 })
129
130 const anyoneCanReply =
131 threadgateAllowUISettings.length === 1 &&
132 threadgateAllowUISettings[0].type === 'everybody'
133 const anyoneCanQuote =
134 !postgate.embeddingRules || postgate.embeddingRules.length === 0
135 const anyoneCanInteract = anyoneCanReply && anyoneCanQuote
136 const label = anyoneCanInteract
137 ? _(msg`Anyone can interact`)
138 : _(msg`Interaction limited`)
139
140 return (
141 <>
142 <Tooltip.Outer
143 visible={showTooltip}
144 onVisibleChange={onDismissTooltip}
145 position="top">
146 <Tooltip.Target>
147 <Button
148 color={showTooltip ? 'primary_subtle' : 'secondary'}
149 size="small"
150 testID="openReplyGateButton"
151 onPress={onPress}
152 label={label}
153 accessibilityHint={_(
154 msg`Opens a dialog to choose who can interact with this post`,
155 )}>
156 <ButtonIcon icon={anyoneCanInteract ? EarthIcon : GroupIcon} />
157 <ButtonText numberOfLines={1}>{label}</ButtonText>
158 <ButtonIcon icon={TinyChevronIcon} size="2xs" />
159 </Button>
160 </Tooltip.Target>
161 <Tooltip.TextBubble>
162 <Text>
163 <Trans>Psst! You can edit who can interact with this post.</Trans>
164 </Text>
165 </Tooltip.TextBubble>
166 </Tooltip.Outer>
167
168 <PostInteractionSettingsControlledDialog
169 control={control}
170 onSave={() => {
171 if (persist) {
172 persistChanges({
173 threadgateAllowRules: threadgateAllowUISettingToAllowRecordValue(
174 threadgateAllowUISettings,
175 ),
176 postgateEmbeddingRules: postgate.embeddingRules ?? [],
177 })
178 } else {
179 control.close()
180 }
181 }}
182 isSaving={isSaving}
183 postgate={postgate}
184 onChangePostgate={onChangePostgate}
185 threadgateAllowUISettings={threadgateAllowUISettings}
186 onChangeThreadgateAllowUISettings={onChangeThreadgateAllowUISettings}
187 isDirty={isDirty}
188 persist={persist}
189 onChangePersist={setPersist}
190 />
191 </>
192 )
193}