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