forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {Keyboard, View} from 'react-native'
2import {
3 type AppBskyActorDefs,
4 type AppBskyFeedDefs,
5 moderateFeedGenerator,
6 moderateProfile,
7 type ModerationOpts,
8 type ModerationUI,
9} from '@atproto/api'
10import {msg, Trans} from '@lingui/macro'
11import {useLingui} from '@lingui/react'
12
13import {DISCOVER_FEED_URI, STARTER_PACK_MAX_SIZE} from '#/lib/constants'
14import {sanitizeDisplayName} from '#/lib/strings/display-names'
15import {sanitizeHandle} from '#/lib/strings/handles'
16import {logger} from '#/logger'
17import {useSession} from '#/state/session'
18import {UserAvatar} from '#/view/com/util/UserAvatar'
19import {
20 type WizardAction,
21 type WizardState,
22} from '#/screens/StarterPack/Wizard/State'
23import {atoms as a, useTheme} from '#/alf'
24import {Button, ButtonText} from '#/components/Button'
25import * as Toggle from '#/components/forms/Toggle'
26import {Checkbox} from '#/components/forms/Toggle'
27import {Text} from '#/components/Typography'
28import type * as bsky from '#/types/bsky'
29
30function WizardListCard({
31 type,
32 btnType,
33 displayName,
34 subtitle,
35 onPress,
36 avatar,
37 included,
38 disabled,
39 moderationUi,
40}: {
41 type: 'user' | 'algo'
42 btnType: 'checkbox' | 'remove'
43 profile?: AppBskyActorDefs.ProfileViewBasic
44 feed?: AppBskyFeedDefs.GeneratorView
45 displayName: string
46 subtitle: string
47 onPress: () => void
48 avatar?: string
49 included?: boolean
50 disabled?: boolean
51 moderationUi: ModerationUI
52}) {
53 const t = useTheme()
54 const {_} = useLingui()
55
56 return (
57 <Toggle.Item
58 name={type === 'user' ? _(msg`Person toggle`) : _(msg`Feed toggle`)}
59 label={
60 included
61 ? _(msg`Remove ${displayName} from starter pack`)
62 : _(msg`Add ${displayName} to starter pack`)
63 }
64 value={included}
65 disabled={btnType === 'remove' || disabled}
66 onChange={onPress}
67 style={[
68 a.flex_row,
69 a.align_center,
70 a.px_lg,
71 a.py_md,
72 a.gap_md,
73 a.border_b,
74 t.atoms.border_contrast_low,
75 ]}>
76 <UserAvatar
77 size={45}
78 avatar={avatar}
79 moderation={moderationUi}
80 type={type}
81 />
82 <View style={[a.flex_1, a.gap_2xs]}>
83 <Text
84 emoji
85 style={[
86 a.flex_1,
87 a.font_semi_bold,
88 a.text_md,
89 a.leading_tight,
90 a.self_start,
91 ]}
92 numberOfLines={1}>
93 {displayName}
94 </Text>
95 <Text
96 style={[a.flex_1, a.leading_tight, t.atoms.text_contrast_medium]}
97 numberOfLines={1}>
98 {subtitle}
99 </Text>
100 </View>
101 {btnType === 'checkbox' ? (
102 <Checkbox />
103 ) : !disabled ? (
104 <Button
105 label={_(msg`Remove`)}
106 variant="solid"
107 color="secondary"
108 size="small"
109 style={[a.self_center, {marginLeft: 'auto'}]}
110 onPress={onPress}>
111 <ButtonText>
112 <Trans>Remove</Trans>
113 </ButtonText>
114 </Button>
115 ) : null}
116 </Toggle.Item>
117 )
118}
119
120export function WizardProfileCard({
121 btnType,
122 state,
123 dispatch,
124 profile,
125 moderationOpts,
126}: {
127 btnType: 'checkbox' | 'remove'
128 state: WizardState
129 dispatch: (action: WizardAction) => void
130 profile: bsky.profile.AnyProfileView
131 moderationOpts: ModerationOpts
132}) {
133 const {currentAccount} = useSession()
134
135 // Determine the "main" profile for this starter pack - either targetDid or current account
136 const targetProfileDid = state.targetDid || currentAccount?.did
137 const isTarget = profile.did === targetProfileDid
138 const included = isTarget || state.profiles.some(p => p.did === profile.did)
139 const disabled =
140 isTarget ||
141 (!included && state.profiles.length >= STARTER_PACK_MAX_SIZE - 1)
142 const moderationUi = moderateProfile(profile, moderationOpts).ui('avatar')
143 const displayName = profile.displayName
144 ? sanitizeDisplayName(profile.displayName)
145 : `@${sanitizeHandle(profile.handle)}`
146
147 const onPress = () => {
148 if (disabled) return
149
150 Keyboard.dismiss()
151 if (profile.did === targetProfileDid) return
152
153 if (!included) {
154 logger.metric('starterPack:addUser', {})
155 dispatch({type: 'AddProfile', profile})
156 } else {
157 logger.metric('starterPack:removeUser', {})
158 dispatch({type: 'RemoveProfile', profileDid: profile.did})
159 }
160 }
161
162 return (
163 <WizardListCard
164 type="user"
165 btnType={btnType}
166 displayName={displayName}
167 subtitle={`@${sanitizeHandle(profile.handle)}`}
168 onPress={onPress}
169 avatar={profile.avatar}
170 included={included}
171 disabled={disabled}
172 moderationUi={moderationUi}
173 />
174 )
175}
176
177export function WizardFeedCard({
178 btnType,
179 generator,
180 state,
181 dispatch,
182 moderationOpts,
183}: {
184 btnType: 'checkbox' | 'remove'
185 generator: AppBskyFeedDefs.GeneratorView
186 state: WizardState
187 dispatch: (action: WizardAction) => void
188 moderationOpts: ModerationOpts
189}) {
190 const isDiscover = generator.uri === DISCOVER_FEED_URI
191 const included = isDiscover || state.feeds.some(f => f.uri === generator.uri)
192 const disabled = isDiscover || (!included && state.feeds.length >= 3)
193 const moderationUi = moderateFeedGenerator(generator, moderationOpts).ui(
194 'avatar',
195 )
196
197 const onPress = () => {
198 if (disabled) return
199
200 Keyboard.dismiss()
201 if (included) {
202 dispatch({type: 'RemoveFeed', feedUri: generator.uri})
203 } else {
204 dispatch({type: 'AddFeed', feed: generator})
205 }
206 }
207
208 return (
209 <WizardListCard
210 type="algo"
211 btnType={btnType}
212 displayName={sanitizeDisplayName(generator.displayName)}
213 subtitle={`Feed by @${sanitizeHandle(generator.creator.handle)}`}
214 onPress={onPress}
215 avatar={generator.avatar}
216 included={included}
217 disabled={disabled}
218 moderationUi={moderationUi}
219 />
220 )
221}