Something went wrong. Try again.
Bluesky app fork with some witchin' additions 💫 forked from jollywhoppers.com/witchsky.app
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301import React, {useCallback} from 'react'import { ActivityIndicator, StyleSheet, useWindowDimensions, View,} from 'react-native'import {type AppBskyGraphDefs as GraphDefs} from '@atproto/api'import {msg, Trans} from '@lingui/macro'import {useLingui} from '@lingui/react'
import {usePalette} from '#/lib/hooks/usePalette'import {sanitizeDisplayName} from '#/lib/strings/display-names'import {cleanError} from '#/lib/strings/errors'import {sanitizeHandle} from '#/lib/strings/handles'import {s} from '#/lib/styles'import {useModalControls} from '#/state/modals'import { getMembership, type ListMembersip, useDangerousListMembershipsQuery, useListMembershipAddMutation, useListMembershipRemoveMutation,} from '#/state/queries/list-memberships'import {useSession} from '#/state/session'import {useTheme} from '#/alf'import {IS_ANDROID, IS_WEB, IS_WEB_MOBILE} from '#/env'import {MyLists} from '../lists/MyLists'import {Button} from '../util/forms/Button'import {Text} from '../util/text/Text'import * as Toast from '../util/Toast'import {UserAvatar} from '../util/UserAvatar'
export const snapPoints = ['fullscreen']
export function Component({ subject, handle, displayName, onAdd, onRemove,}: { subject: string handle: string displayName: string onAdd?: (listUri: string) => void onRemove?: (listUri: string) => void}) { const {closeModal} = useModalControls() const pal = usePalette('default') const {height: screenHeight} = useWindowDimensions() const {_} = useLingui() const {data: memberships} = useDangerousListMembershipsQuery()
const onPressDone = useCallback(() => { closeModal() }, [closeModal])
const listStyle = React.useMemo(() => { if (IS_WEB_MOBILE) { return [pal.border, {height: screenHeight / 2}] } else if (IS_WEB) { return [pal.border, {height: screenHeight / 1.5}] }
return [pal.border, {flex: 1, borderTopWidth: StyleSheet.hairlineWidth}] }, [pal.border, screenHeight])
const headerStyles = [ { textAlign: 'center', fontWeight: '600', fontSize: 20, marginBottom: 12, paddingHorizontal: 12, } as const, pal.text, ]
return ( <View testID="userAddRemoveListsModal" style={s.hContentRegion}> <Text style={headerStyles} numberOfLines={1}> <Trans> Update{' '} <Text style={headerStyles} numberOfLines={1}> {displayName} </Text>{' '} in Lists </Trans> </Text> <MyLists filter="all" inline renderItem={(list, index) => ( <ListItem key={list.uri} index={index} list={list} memberships={memberships} subject={subject} handle={handle} onAdd={onAdd} onRemove={onRemove} /> )} style={listStyle} /> <View style={[styles.btns, pal.border]}> <Button testID="doneBtn" type="default" onPress={onPressDone} style={styles.footerBtn} accessibilityLabel={_(msg({message: `Done`, context: 'action'}))} accessibilityHint="" onAccessibilityEscape={onPressDone} label={_(msg({message: `Done`, context: 'action'}))} /> </View> </View> )}
function ListItem({ index, list, memberships, subject, handle, onAdd, onRemove,}: { index: number list: GraphDefs.ListView memberships: ListMembersip[] | undefined subject: string handle: string onAdd?: (listUri: string) => void onRemove?: (listUri: string) => void}) { const t = useTheme(); const pal = usePalette('default') const {_} = useLingui() const {currentAccount} = useSession() const [isProcessing, setIsProcessing] = React.useState(false) const membership = React.useMemo( () => getMembership(memberships, list.uri, subject), [memberships, list.uri, subject], ) const listMembershipAddMutation = useListMembershipAddMutation() const listMembershipRemoveMutation = useListMembershipRemoveMutation()
const onToggleMembership = useCallback(async () => { if (typeof membership === 'undefined') { return } setIsProcessing(true) try { if (membership === false) { await listMembershipAddMutation.mutateAsync({ listUri: list.uri, actorDid: subject, }) Toast.show(_(msg`Added to list`)) onAdd?.(list.uri) } else { await listMembershipRemoveMutation.mutateAsync({ listUri: list.uri, actorDid: subject, membershipUri: membership, }) Toast.show(_(msg`Removed from list`)) onRemove?.(list.uri) } } catch (e) { Toast.show(cleanError(e), 'xmark') } finally { setIsProcessing(false) } }, [ _, list, subject, membership, setIsProcessing, onAdd, onRemove, listMembershipAddMutation, listMembershipRemoveMutation, ])
return ( <View testID={`toggleBtn-${list.name}`} style={[ styles.listItem, pal.border, index !== 0 && {borderTopWidth: StyleSheet.hairlineWidth}, ]}> <View style={styles.listItemAvi}> <UserAvatar size={40} avatar={list.avatar} type="list" /> </View> <View style={styles.listItemContent}> <Text type="lg" style={[s.bold, pal.text]} numberOfLines={1} lineHeight={1.2}> {sanitizeDisplayName(list.name)} </Text> <Text type="md" style={[pal.textLight]} numberOfLines={1}> {list.purpose === 'app.bsky.graph.defs#curatelist' && (list.creator.did === currentAccount?.did ? ( <Trans>User list by you</Trans> ) : ( <Trans> User list by {sanitizeHandle(list.creator.handle, '@')} </Trans> ))} {list.purpose === 'app.bsky.graph.defs#modlist' && (list.creator.did === currentAccount?.did ? ( <Trans>Moderation list by you</Trans> ) : ( <Trans> Moderation list by {sanitizeHandle(list.creator.handle, '@')} </Trans> ))} </Text> </View> <View> {isProcessing || typeof membership === 'undefined' ? ( <ActivityIndicator color={t.palette.contrast_500} /> ) : ( <Button testID={`user-${handle}-addBtn`} type="default" label={membership === false ? _(msg`Add`) : _(msg`Remove`)} onPress={onToggleMembership} /> )} </View> </View> )}
const styles = StyleSheet.create({ container: { paddingHorizontal: IS_WEB ? 0 : 16, }, btns: { position: 'relative', flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 10, paddingTop: 10, paddingBottom: IS_ANDROID ? 10 : 0, borderTopWidth: StyleSheet.hairlineWidth, }, footerBtn: { paddingHorizontal: 24, paddingVertical: 12, },
listItem: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 14, paddingVertical: 10, }, listItemAvi: { width: 54, paddingLeft: 4, paddingTop: 8, paddingBottom: 10, }, listItemContent: { flex: 1, paddingRight: 10, paddingTop: 10, paddingBottom: 10, }, checkbox: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', borderWidth: 1, width: 24, height: 24, borderRadius: 6, marginRight: 8, }, loadingContainer: { position: 'absolute', top: 10, right: 0, bottom: 0, justifyContent: 'center', },})