Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
at main 193 lines 6.3 kB view raw
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}