import {useCallback, useMemo, useState} from 'react'
import {useWindowDimensions, View} from 'react-native'
import {type AppBskyGraphDefs, type ModerationOpts} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {cleanError} from '#/lib/strings/errors'
import {sanitizeHandle} from '#/lib/strings/handles'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {
getMembership,
type ListMembersip,
useDangerousListMembershipsQuery,
useListMembershipAddMutation,
useListMembershipRemoveMutation,
} from '#/state/queries/list-memberships'
import {useMyListsQuery} from '#/state/queries/my-lists'
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
import * as Toast from '#/view/com/util/Toast'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, useTheme, web} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {BulletList_Stroke2_Corner0_Rounded as ListIcon} from '#/components/icons/BulletList'
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
import {Loader} from '#/components/Loader'
import * as ProfileCard from '#/components/ProfileCard'
import {Text} from '#/components/Typography'
import type * as bsky from '#/types/bsky'
export function UserAddRemoveListsDialog({
control,
profile,
onChange,
}: {
control: Dialog.DialogControlProps
profile: bsky.profile.AnyProfileView
onChange?: (
type: 'add' | 'remove',
list: AppBskyGraphDefs.ListView,
) => void | undefined
}) {
const {height} = useWindowDimensions()
return (
)
}
const LOADING = {type: 'loading', _reactKey: '__loading__'} as const
const EMPTY = {type: 'empty', _reactKey: '__empty__'} as const
const ERROR_ITEM = {type: 'error', _reactKey: '__error__'} as const
type ListItem =
| typeof LOADING
| typeof EMPTY
| typeof ERROR_ITEM
| {
type: 'section-header'
title: string
_reactKey: string
}
| {
type: 'list'
list: AppBskyGraphDefs.ListView
}
function DialogInner({
profile,
onChange,
}: {
profile: bsky.profile.AnyProfileView
onChange?: (
type: 'add' | 'remove',
list: AppBskyGraphDefs.ListView,
) => void | undefined
}) {
const t = useTheme()
const {_} = useLingui()
const control = Dialog.useDialogContext()
const moderationOpts = useModerationOpts()
const [headerHeight, setHeaderHeight] = useState(50)
const {data, isPending, isError, error} = useMyListsQuery('all')
const {data: memberships} = useDangerousListMembershipsQuery()
const isEmpty = !isPending && !data?.length
const items = useMemo(() => {
let _items: ListItem[] = []
if (isError && isEmpty) {
_items = _items.concat([ERROR_ITEM])
}
if (isPending || !moderationOpts) {
_items = _items.concat([LOADING])
} else if (isEmpty) {
_items = _items.concat([EMPTY])
} else if (data) {
// TODO: Add starter packs! -sfn
const curateLists = data.filter(
list => list.purpose === 'app.bsky.graph.defs#curatelist',
)
const modLists = data.filter(
list => list.purpose === 'app.bsky.graph.defs#modlist',
)
if (curateLists.length > 0) {
_items = _items.concat(
{
type: 'section-header',
title: _(msg`User lists`),
_reactKey: 'curatelist',
},
curateLists.map(list => ({type: 'list', list})),
)
}
if (modLists.length > 0) {
_items = _items.concat(
{
type: 'section-header',
title: _(msg`Moderation lists`),
_reactKey: 'modlist',
},
modLists.map(list => ({type: 'list', list})),
)
}
}
return _items
}, [isError, isEmpty, isPending, moderationOpts, data, _])
const renderItem = useCallback(
({item}: {item: ListItem}) => {
switch (item.type) {
case 'empty': {
return (
You have no lists.
)
}
case 'error':
return
case 'loading':
return (
)
case 'section-header':
return (
{item.title}
)
case 'list':
return (
)
}
return null
},
[t, error, memberships, onChange, profile, moderationOpts],
)
const renderCloseButton = useCallback(
() => (
),
[control, _],
)
return (
setHeaderHeight(e.nativeEvent.layout.height)}
renderRight={renderCloseButton}>
Add{' '}
{sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle),
)}{' '}
to lists
}
/>
)
}
function keyExtractor(item: ListItem) {
return item.type === 'list' ? item.list.uri : item._reactKey
}
function ListRow({
profile,
list,
memberships,
onChange,
moderationOpts,
}: {
profile: bsky.profile.AnyProfileView
list: AppBskyGraphDefs.ListView
memberships: ListMembersip[] | undefined
onChange?: (
type: 'add' | 'remove',
list: AppBskyGraphDefs.ListView,
) => void | undefined
moderationOpts?: ModerationOpts
}) {
const t = useTheme()
const {_} = useLingui()
const membership = useMemo(
() => getMembership(memberships, list.uri, profile.did),
[memberships, list.uri, profile.did],
)
const {mutate: listMembershipAdd, isPending: isAddingPending} =
useListMembershipAddMutation({
onSuccess: () => {
Toast.show(_(msg`Added to list`))
onChange?.('add', list)
},
onError: e => Toast.show(cleanError(e), 'xmark'),
})
const {mutate: listMembershipRemove, isPending: isRemovingPending} =
useListMembershipRemoveMutation({
onSuccess: () => {
Toast.show(_(msg`Removed from list`))
onChange?.('remove', list)
},
onError: e => Toast.show(cleanError(e), 'xmark'),
})
const isMutating = isAddingPending || isRemovingPending
const onToggleMembership = useCallback(() => {
if (typeof membership === 'undefined') {
return
}
if (membership === false) {
listMembershipAdd({
listUri: list.uri,
actorDid: profile.did,
})
} else {
listMembershipRemove({
listUri: list.uri,
actorDid: profile.did,
membershipUri: membership,
})
}
}, [list, profile, membership, listMembershipAdd, listMembershipRemove])
if (!moderationOpts) return null
return (
{sanitizeDisplayName(list.name)}
{list.purpose === 'app.bsky.graph.defs#curatelist' && (
User list
)}
{list.purpose === 'app.bsky.graph.defs#modlist' && (
Moderation list
)}
{membership !== undefined && (
)}
)
}