import React, {useCallback} from 'react' import {observer} from 'mobx-react-lite' import {ActivityIndicator, Pressable, StyleSheet, View} from 'react-native' import {AppBskyGraphDefs as GraphDefs} from '@atproto/api' import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' import {Text} from '../util/text/Text' import {UserAvatar} from '../util/UserAvatar' import {ListsList} from '../lists/ListsList' import {ListsListModel} from 'state/models/lists/lists-list' import {ListMembershipModel} from 'state/models/content/list-membership' import {Button} from '../util/forms/Button' import * as Toast from '../util/Toast' import {useStores} from 'state/index' import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' import {s} from 'lib/styles' import {usePalette} from 'lib/hooks/usePalette' import {isWeb, isAndroid} from 'platform/detection' import isEqual from 'lodash.isequal' import {logger} from '#/logger' export const snapPoints = ['fullscreen'] export const Component = observer(function UserAddRemoveListsImpl({ subject, displayName, onAdd, onRemove, }: { subject: string displayName: string onAdd?: (listUri: string) => void onRemove?: (listUri: string) => void }) { const store = useStores() const pal = usePalette('default') const palPrimary = usePalette('primary') const palInverted = usePalette('inverted') const [originalSelections, setOriginalSelections] = React.useState( [], ) const [selected, setSelected] = React.useState([]) const [membershipsLoaded, setMembershipsLoaded] = React.useState(false) const listsList: ListsListModel = React.useMemo( () => new ListsListModel(store, store.me.did), [store], ) const memberships: ListMembershipModel = React.useMemo( () => new ListMembershipModel(store, subject), [store, subject], ) React.useEffect(() => { listsList.refresh() memberships.fetch().then( () => { const ids = memberships.memberships.map(m => m.value.list) setOriginalSelections(ids) setSelected(ids) setMembershipsLoaded(true) }, err => { logger.error('Failed to fetch memberships', {error: err}) }, ) }, [memberships, listsList, store, setSelected, setMembershipsLoaded]) const onPressCancel = useCallback(() => { store.shell.closeModal() }, [store]) const onPressSave = useCallback(async () => { let changes try { changes = await memberships.updateTo(selected) } catch (err) { logger.error('Failed to update memberships', {error: err}) return } Toast.show('Lists updated') for (const uri of changes.added) { onAdd?.(uri) } for (const uri of changes.removed) { onRemove?.(uri) } store.shell.closeModal() }, [store, selected, memberships, onAdd, onRemove]) const onToggleSelected = useCallback( (uri: string) => { if (selected.includes(uri)) { setSelected(selected.filter(uri2 => uri2 !== uri)) } else { setSelected([...selected, uri]) } }, [selected, setSelected], ) const renderItem = useCallback( (list: GraphDefs.ListView, index: number) => { const isSelected = selected.includes(list.uri) return ( onToggleSelected(list.uri)}> {sanitizeDisplayName(list.name)} {list.purpose === 'app.bsky.graph.defs#curatelist' && 'User list '} {list.purpose === 'app.bsky.graph.defs#modlist' && 'Moderation list '} by{' '} {list.creator.did === store.me.did ? 'you' : sanitizeHandle(list.creator.handle, '@')} {membershipsLoaded && ( {isSelected && ( )} )} ) }, [ pal, palPrimary, palInverted, onToggleSelected, selected, store.me.did, membershipsLoaded, ], ) // Only show changes button if there are some items on the list to choose from AND user has made changes in selection const canSaveChanges = !listsList.isEmpty && !isEqual(selected, originalSelections) return ( Update {displayName} in Lists