diff --git a/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx b/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx
index da96565655..8129e9bdb4 100644
--- a/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx
+++ b/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx
@@ -7,10 +7,8 @@ import {Trans} from '@lingui/react/macro'
import {cleanError} from '#/lib/strings/errors'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
+import {useAllListMembersQuery} from '#/state/queries/list-members'
import {
- getMembership,
- type ListMembersip,
- useDangerousListMembershipsQuery,
useListMembershipAddMutation,
useListMembershipRemoveMutation,
} from '#/state/queries/list-memberships'
@@ -61,7 +59,7 @@ function DialogInner({
}) {
const {_} = useLingui()
const moderationOpts = useModerationOpts()
- const {data: memberships} = useDangerousListMembershipsQuery()
+ const {data: listMembers} = useAllListMembersQuery(list.uri)
const renderProfileCard = useCallback(
(item: ProfileItem) => {
@@ -69,13 +67,13 @@ function DialogInner({
)
},
- [onChange, memberships, list, moderationOpts],
+ [onChange, listMembers, list, moderationOpts],
)
return (
@@ -86,16 +84,30 @@ function DialogInner({
)
}
+/**
+ * Returns undefined for pending, false for not a member, and string for a member (the URI of the membership record)
+ */
+function getMembership(
+ listMembers: AppBskyGraphDefs.ListItemView[] | undefined,
+ actorDid: string,
+): string | false | undefined {
+ if (!listMembers) {
+ return undefined
+ }
+ const member = listMembers.find(item => item.subject.did === actorDid)
+ return member ? member.uri : false
+}
+
function UserResult({
profile,
list,
- memberships,
+ listMembers,
onChange,
moderationOpts,
}: {
profile: bsky.profile.AnyProfileView
list: AppBskyGraphDefs.ListView
- memberships: ListMembersip[] | undefined
+ listMembers: AppBskyGraphDefs.ListItemView[] | undefined
onChange?: (
type: 'add' | 'remove',
profile: bsky.profile.AnyProfileView,
@@ -104,8 +116,8 @@ function UserResult({
}) {
const {_} = useLingui()
const membership = useMemo(
- () => getMembership(memberships, list.uri, profile.did),
- [memberships, list.uri, profile.did],
+ () => getMembership(listMembers, profile.did),
+ [listMembers, profile.did],
)
const {mutate: listMembershipAdd, isPending: isAddingPending} =
useListMembershipAddMutation({
diff --git a/src/components/dialogs/lists/UserAddRemoveListsDialog.tsx b/src/components/dialogs/lists/UserAddRemoveListsDialog.tsx
new file mode 100644
index 0000000000..ad16a445ff
--- /dev/null
+++ b/src/components/dialogs/lists/UserAddRemoveListsDialog.tsx
@@ -0,0 +1,334 @@
+import {useCallback} from 'react'
+import {View} from 'react-native'
+import {type AppBskyActorDefs} from '@atproto/api'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useQueryClient} from '@tanstack/react-query'
+
+import {sanitizeDisplayName} from '#/lib/strings/display-names'
+import {isNetworkError} from '#/lib/strings/errors'
+import {sanitizeHandle} from '#/lib/strings/handles'
+import {logger} from '#/logger'
+import {
+ useListMembershipAddMutation,
+ useListMembershipRemoveMutation,
+} from '#/state/queries/list-memberships'
+import {
+ type ListWithMembership,
+ removeListMembershipOptimistically,
+ updateListMembershipOptimistically,
+ useListsWithMembershipQuery,
+} from '#/state/queries/lists-with-membership'
+import {useSession} from '#/state/session'
+import {UserAvatar} from '#/view/com/util/UserAvatar'
+import {atoms as a, native, platform, useTheme} 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 Toast from '#/components/Toast'
+import {Text} from '#/components/Typography'
+
+export type UserAddRemoveListsDialogProps = {
+ control: Dialog.DialogControlProps
+ subjectDid: string
+ displayName: string
+ handle: string
+ onAdd?: (listUri: string) => void
+ onRemove?: (listUri: string) => void
+}
+
+export function UserAddRemoveListsDialog({
+ control,
+ subjectDid,
+ displayName,
+ handle,
+ onAdd,
+ onRemove,
+}: UserAddRemoveListsDialogProps) {
+ return (
+
+
+
+
+ )
+}
+
+function Empty() {
+ const t = useTheme()
+
+ return (
+
+
+
+
+ You have no lists.
+
+
+
+ )
+}
+
+function ListsContent({
+ subjectDid,
+ displayName,
+ handle,
+ onAdd,
+ onRemove,
+}: Omit) {
+ const control = Dialog.useDialogContext()
+ const {_} = useLingui()
+
+ const {
+ data,
+ isError,
+ isLoading,
+ hasNextPage,
+ isFetchingNextPage,
+ fetchNextPage,
+ } = useListsWithMembershipQuery({actor: subjectDid})
+
+ const listItems = data?.pages.flatMap(page => page.listsWithMembership) || []
+
+ const onEndReached = useCallback(async () => {
+ if (isFetchingNextPage || !hasNextPage || isError) return
+ try {
+ await fetchNextPage()
+ } catch (err) {
+ // Error handling is optional since this is just pagination
+ }
+ }, [isFetchingNextPage, hasNextPage, isError, fetchNextPage])
+
+ const renderItem = useCallback(
+ ({item}: {item: ListWithMembership}) => (
+
+ ),
+ [subjectDid, displayName, handle, onAdd, onRemove],
+ )
+
+ const onClose = useCallback(() => {
+ control.close()
+ }, [control])
+
+ const listHeader = (
+
+
+ Update {sanitizeDisplayName(displayName)} in Lists
+
+
+
+ )
+
+ return (
+ (
+
+
+
+ )
+ : renderItem
+ }
+ keyExtractor={
+ isLoading
+ ? () => 'lists_dialog_loader'
+ : (item: ListWithMembership) => item.list.uri
+ }
+ onEndReached={onEndReached}
+ onEndReachedThreshold={0.1}
+ ListHeaderComponent={listHeader}
+ ListEmptyComponent={}
+ style={platform({
+ web: [a.px_2xl, {minHeight: 400}],
+ native: [a.px_2xl, a.pt_lg],
+ })}
+ />
+ )
+}
+
+function ListItem({
+ listWithMembership,
+ subjectDid,
+ displayName,
+ handle,
+ onAdd,
+ onRemove,
+}: {
+ listWithMembership: ListWithMembership
+ subjectDid: string
+ displayName: string
+ handle: string
+ onAdd?: (listUri: string) => void
+ onRemove?: (listUri: string) => void
+}) {
+ const {_} = useLingui()
+ const t = useTheme()
+ const queryClient = useQueryClient()
+ const {currentAccount} = useSession()
+
+ const list = listWithMembership.list
+ const listItem = listWithMembership.listItem
+ const isMember = !!listItem
+
+ const {mutate: addMembership, isPending: isPendingAdd} =
+ useListMembershipAddMutation({
+ onSuccess: data => {
+ Toast.show(_(msg`Added to list`))
+ onAdd?.(list.uri)
+ updateListMembershipOptimistically({
+ queryClient,
+ actor: subjectDid,
+ listUri: list.uri,
+ membershipUri: data.uri,
+ subject: {
+ did: subjectDid,
+ handle,
+ displayName,
+ } as AppBskyActorDefs.ProfileView,
+ })
+ },
+ onError: err => {
+ if (!isNetworkError(err)) {
+ logger.error('Failed to add to list', {safeMessage: err})
+ }
+ Toast.show(_(msg`Failed to add to list`), {type: 'error'})
+ },
+ })
+
+ const {mutate: removeMembership, isPending: isPendingRemove} =
+ useListMembershipRemoveMutation({
+ onSuccess: () => {
+ Toast.show(_(msg`Removed from list`))
+ onRemove?.(list.uri)
+ removeListMembershipOptimistically({
+ queryClient,
+ actor: subjectDid,
+ listUri: list.uri,
+ })
+ },
+ onError: err => {
+ if (!isNetworkError(err)) {
+ logger.error('Failed to remove from list', {safeMessage: err})
+ }
+ Toast.show(_(msg`Failed to remove from list`), {type: 'error'})
+ },
+ })
+
+ const isPending = isPendingAdd || isPendingRemove
+
+ const handleToggleMembership = useCallback(() => {
+ if (isPending) return
+
+ if (!isMember) {
+ addMembership({
+ listUri: list.uri,
+ actorDid: subjectDid,
+ })
+ } else {
+ if (!listItem?.uri) {
+ console.error('Cannot remove: missing membership URI')
+ return
+ }
+ removeMembership({
+ listUri: list.uri,
+ actorDid: subjectDid,
+ membershipUri: listItem.uri,
+ })
+ }
+ }, [
+ list.uri,
+ subjectDid,
+ isMember,
+ listItem,
+ isPending,
+ addMembership,
+ removeMembership,
+ ])
+
+ return (
+
+
+
+
+ {sanitizeDisplayName(list.name)}
+
+
+ {list.purpose === 'app.bsky.graph.defs#curatelist' &&
+ (list.creator.did === currentAccount?.did ? (
+ User list by you
+ ) : (
+
+ User list by {sanitizeHandle(list.creator.handle, '@')}
+
+ ))}
+ {list.purpose === 'app.bsky.graph.defs#modlist' &&
+ (list.creator.did === currentAccount?.did ? (
+ Moderation list by you
+ ) : (
+
+ Moderation list by {sanitizeHandle(list.creator.handle, '@')}
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/src/state/modals/index.tsx b/src/state/modals/index.tsx
index 3c545362b8..d62ba2a7a0 100644
--- a/src/state/modals/index.tsx
+++ b/src/state/modals/index.tsx
@@ -3,15 +3,6 @@ import {createContext, useContext, useEffect, useMemo, useState} from 'react'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {useHotkeysContext} from '#/lib/hotkeys'
-export interface UserAddRemoveListsModal {
- name: 'user-add-remove-lists'
- subject: string
- handle: string
- displayName: string
- onAdd?: (listUri: string) => void
- onRemove?: (listUri: string) => void
-}
-
export interface ContentLanguagesSettingsModal {
name: 'content-languages-settings'
}
@@ -21,10 +12,7 @@ export interface ContentLanguagesSettingsModal {
*/
export type Modal =
// Curation
- | ContentLanguagesSettingsModal
-
- // Lists
- | UserAddRemoveListsModal
+ ContentLanguagesSettingsModal
const ModalContext = createContext<{
isModalActive: boolean
diff --git a/src/state/queries/list-memberships.ts b/src/state/queries/list-memberships.ts
index dd7fde2482..ab5221a80b 100644
--- a/src/state/queries/list-memberships.ts
+++ b/src/state/queries/list-memberships.ts
@@ -1,19 +1,3 @@
-/**
- * NOTE
- *
- * This query is a temporary solution to our lack of server API for
- * querying user membership in an API. It is extremely inefficient.
- *
- * THIS SHOULD ONLY BE USED IN MODALS FOR MODIFYING A USER'S LIST MEMBERSHIP!
- * Use the list-members query for rendering a list's members.
- *
- * It works by fetching *all* of the user's list item records and querying
- * or manipulating that cache. For users with large lists, it will fall
- * down completely, so be very conservative about how you use it.
- *
- * -prf
- */
-
import {
type AppBskyActorDefs,
type AppBskyGraphGetStarterPacksWithMembership,
@@ -22,85 +6,14 @@ import {
import {
type InfiniteData,
useMutation,
- useQuery,
useQueryClient,
} from '@tanstack/react-query'
-import {STALE} from '#/state/queries'
import {RQKEY as LIST_MEMBERS_RQKEY} from '#/state/queries/list-members'
import {useAgent, useSession} from '#/state/session'
import type * as bsky from '#/types/bsky'
import {RQKEY_WITH_MEMBERSHIP as STARTER_PACKS_WITH_MEMBERSHIPS_RKEY} from './actor-starter-packs'
-// sanity limit is SANITY_PAGE_LIMIT*PAGE_SIZE total records
-const SANITY_PAGE_LIMIT = 1000
-const PAGE_SIZE = 100
-// ...which comes 100,000k list members
-
-const RQKEY_ROOT = 'list-memberships'
-export const RQKEY = () => [RQKEY_ROOT]
-
-export interface ListMembersip {
- membershipUri: string
- listUri: string
- actorDid: string
-}
-
-/**
- * This API is dangerous! Read the note above!
- */
-export function useDangerousListMembershipsQuery() {
- const {currentAccount} = useSession()
- const agent = useAgent()
- return useQuery({
- staleTime: STALE.MINUTES.FIVE,
- queryKey: RQKEY(),
- async queryFn() {
- if (!currentAccount) {
- return []
- }
- let cursor
- let arr: ListMembersip[] = []
- for (let i = 0; i < SANITY_PAGE_LIMIT; i++) {
- const res = await agent.app.bsky.graph.listitem.list({
- repo: currentAccount.did,
- limit: PAGE_SIZE,
- cursor,
- })
- arr = arr.concat(
- res.records.map(r => ({
- membershipUri: r.uri,
- listUri: r.value.list,
- actorDid: r.value.subject,
- })),
- )
- cursor = res.cursor
- if (!cursor) {
- break
- }
- }
- return arr
- },
- })
-}
-
-/**
- * Returns undefined for pending, false for not a member, and string for a member (the URI of the membership record)
- */
-export function getMembership(
- memberships: ListMembersip[] | undefined,
- list: string,
- actor: string,
-): string | false | undefined {
- if (!memberships) {
- return undefined
- }
- const membership = memberships.find(
- m => m.listUri === list && m.actorDid === actor,
- )
- return membership ? membership.membershipUri : false
-}
-
export function useListMembershipAddMutation({
subject,
onSuccess,
@@ -133,43 +46,18 @@ export function useListMembershipAddMutation({
createdAt: new Date().toISOString(),
},
)
- // TODO
- // we need to wait for appview to update, but there's not an efficient
- // query for that, so we use a timeout below
- // -prf
return res
},
onSuccess: (data, variables) => {
- // manually update the cache; a refetch is too expensive
- let memberships = queryClient.getQueryData(RQKEY())
- if (memberships) {
- memberships = memberships
- // avoid dups
- .filter(
- m =>
- !(
- m.actorDid === variables.actorDid &&
- m.listUri === variables.listUri
- ),
- )
- .concat([
- {
- ...variables,
- membershipUri: data.uri,
- },
- ])
- queryClient.setQueryData(RQKEY(), memberships)
- }
// invalidate the members queries (used for rendering the listings)
- // use a timeout to wait for the appview (see above)
+ // use a timeout to wait for the appview
setTimeout(() => {
queryClient.invalidateQueries({
queryKey: LIST_MEMBERS_RQKEY(variables.listUri),
})
}, 1e3)
- // update WITH_MEMBERSHIPS query
-
+ // update WITH_MEMBERSHIPS query for starter packs
if (subject) {
queryClient.setQueryData<
InfiniteData
@@ -251,32 +139,17 @@ export function useListMembershipRemoveMutation({
repo: currentAccount.did,
rkey: membershipUrip.rkey,
})
- // TODO
- // we need to wait for appview to update, but there's not an efficient
- // query for that, so we use a timeout below
- // -prf
},
onSuccess: (data, variables) => {
- // manually update the cache; a refetch is too expensive
- let memberships = queryClient.getQueryData(RQKEY())
- if (memberships) {
- memberships = memberships.filter(
- m =>
- !(
- m.actorDid === variables.actorDid &&
- m.listUri === variables.listUri
- ),
- )
- queryClient.setQueryData(RQKEY(), memberships)
- }
// invalidate the members queries (used for rendering the listings)
- // use a timeout to wait for the appview (see above)
+ // use a timeout to wait for the appview
setTimeout(() => {
queryClient.invalidateQueries({
queryKey: LIST_MEMBERS_RQKEY(variables.listUri),
})
}, 1e3)
+ // update WITH_MEMBERSHIPS query for starter packs
queryClient.setQueryData<
InfiniteData
>(STARTER_PACKS_WITH_MEMBERSHIPS_RKEY(variables.actorDid), old => {
diff --git a/src/state/queries/lists-with-membership.ts b/src/state/queries/lists-with-membership.ts
new file mode 100644
index 0000000000..01fef12ad7
--- /dev/null
+++ b/src/state/queries/lists-with-membership.ts
@@ -0,0 +1,120 @@
+import {
+ type AppBskyActorDefs,
+ type AppBskyGraphGetListsWithMembership,
+} from '@atproto/api'
+import {
+ type InfiniteData,
+ type QueryClient,
+ type QueryKey,
+ useInfiniteQuery,
+} from '@tanstack/react-query'
+
+import {useAgent} from '#/state/session'
+
+export type ListWithMembership =
+ AppBskyGraphGetListsWithMembership.ListWithMembership
+
+const RQKEY_ROOT = 'lists-with-membership'
+export const RQKEY = (actor: string) => [RQKEY_ROOT, actor]
+
+export function useListsWithMembershipQuery({
+ actor,
+ enabled = true,
+}: {
+ actor: string
+ enabled?: boolean
+}) {
+ const agent = useAgent()
+
+ return useInfiniteQuery<
+ AppBskyGraphGetListsWithMembership.OutputSchema,
+ Error,
+ InfiniteData,
+ QueryKey,
+ string | undefined
+ >({
+ queryKey: RQKEY(actor),
+ queryFn: async ({pageParam}: {pageParam?: string}) => {
+ const res = await agent.app.bsky.graph.getListsWithMembership({
+ actor,
+ limit: 50,
+ cursor: pageParam,
+ })
+ return res.data
+ },
+ enabled: Boolean(actor) && enabled,
+ initialPageParam: undefined,
+ getNextPageParam: lastPage => lastPage.cursor,
+ })
+}
+
+export function updateListMembershipOptimistically({
+ queryClient,
+ actor,
+ listUri,
+ membershipUri,
+ subject,
+}: {
+ queryClient: QueryClient
+ actor: string
+ listUri: string
+ membershipUri: string
+ subject: AppBskyActorDefs.ProfileView
+}) {
+ queryClient.setQueryData<
+ InfiniteData
+ >(RQKEY(actor), old => {
+ if (!old) return old
+
+ return {
+ ...old,
+ pages: old.pages.map(page => ({
+ ...page,
+ listsWithMembership: page.listsWithMembership.map(lwm => {
+ if (lwm.list.uri === listUri) {
+ return {
+ ...lwm,
+ listItem: {
+ uri: membershipUri,
+ subject,
+ },
+ }
+ }
+ return lwm
+ }),
+ })),
+ }
+ })
+}
+
+export function removeListMembershipOptimistically({
+ queryClient,
+ actor,
+ listUri,
+}: {
+ queryClient: QueryClient
+ actor: string
+ listUri: string
+}) {
+ queryClient.setQueryData<
+ InfiniteData
+ >(RQKEY(actor), old => {
+ if (!old) return old
+
+ return {
+ ...old,
+ pages: old.pages.map(page => ({
+ ...page,
+ listsWithMembership: page.listsWithMembership.map(lwm => {
+ if (lwm.list.uri === listUri) {
+ return {
+ ...lwm,
+ listItem: undefined,
+ }
+ }
+ return lwm
+ }),
+ })),
+ }
+ })
+}
diff --git a/src/view/com/lists/ListMembers.tsx b/src/view/com/lists/ListMembers.tsx
index a443c49fd4..886d105dc1 100644
--- a/src/view/com/lists/ListMembers.tsx
+++ b/src/view/com/lists/ListMembers.tsx
@@ -13,7 +13,6 @@ import {Trans} from '@lingui/react/macro'
import {cleanError} from '#/lib/strings/errors'
import {logger} from '#/logger'
-import {useModalControls} from '#/state/modals'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useListMembersQuery} from '#/state/queries/list-members'
import {useSession} from '#/state/session'
@@ -23,6 +22,8 @@ import {ProfileCardFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlacehol
import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
+import {useDialogControl} from '#/components/Dialog'
+import {UserAddRemoveListsDialog} from '#/components/dialogs/lists/UserAddRemoveListsDialog'
import {ListFooter} from '#/components/Lists'
import * as ProfileCard from '#/components/ProfileCard'
import type * as bsky from '#/types/bsky'
@@ -58,9 +59,12 @@ export function ListMembers({
const t = useTheme()
const {_} = useLingui()
const [isRefreshing, setIsRefreshing] = useState(false)
- const {openModal} = useModalControls()
const {currentAccount} = useSession()
const moderationOpts = useModerationOpts()
+ const editListsDialogControl = useDialogControl()
+ const [selectedProfile, setSelectedProfile] = React.useState<
+ bsky.profile.AnyProfileView | undefined
+ >()
const {
data,
@@ -128,14 +132,10 @@ export function ListMembers({
const onPressEditMembership = useCallback(
(e: GestureResponderEvent, profile: bsky.profile.AnyProfileView) => {
e.preventDefault()
- openModal({
- name: 'user-add-remove-lists',
- subject: profile.did,
- displayName: profile.displayName || profile.handle,
- handle: profile.handle,
- })
+ setSelectedProfile(profile)
+ editListsDialogControl.open()
},
- [openModal],
+ [editListsDialogControl],
)
// rendering
@@ -263,6 +263,15 @@ export function ListMembers({
removeClippedSubviews={true}
desktopFixedHeight={desktopFixedHeightOffset || true}
/>
+
+ {selectedProfile && (
+
+ )}
)
}
diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx
index fa95661d27..96b04a1428 100644
--- a/src/view/com/modals/Modal.tsx
+++ b/src/view/com/modals/Modal.tsx
@@ -7,7 +7,6 @@ import {usePalette} from '#/lib/hooks/usePalette'
import {useModalControls, useModals} from '#/state/modals'
import {FullWindowOverlay} from '#/components/FullWindowOverlay'
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
-import * as UserAddRemoveListsModal from './UserAddRemoveLists'
const DEFAULT_SNAPPOINTS = ['90%']
const HANDLE_HEIGHT = 24
@@ -40,10 +39,7 @@ export function ModalsContainer() {
let snapPoints: (string | number)[] = DEFAULT_SNAPPOINTS
let element
- if (activeModal?.name === 'user-add-remove-lists') {
- snapPoints = UserAddRemoveListsModal.snapPoints
- element =
- } else {
+ {
return null
}
diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx
index dc080bae01..e34999323d 100644
--- a/src/view/com/modals/Modal.web.tsx
+++ b/src/view/com/modals/Modal.web.tsx
@@ -6,7 +6,6 @@ import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {type Modal as ModalIface} from '#/state/modals'
import {useModalControls, useModals} from '#/state/modals'
-import * as UserAddRemoveLists from './UserAddRemoveLists'
export function ModalsContainer() {
const {isModalActive, activeModals} = useModals()
@@ -44,9 +43,7 @@ function Modal({modal}: {modal: ModalIface}) {
}
let element
- if (modal.name === 'user-add-remove-lists') {
- element =
- } else {
+ {
return null
}
diff --git a/src/view/com/profile/ProfileMenu.tsx b/src/view/com/profile/ProfileMenu.tsx
index 51edee1903..34e592751a 100644
--- a/src/view/com/profile/ProfileMenu.tsx
+++ b/src/view/com/profile/ProfileMenu.tsx
@@ -12,7 +12,6 @@ import {type NavigationProp} from '#/lib/routes/types'
import {shareText, shareUrl} from '#/lib/sharing'
import {toShareUrl} from '#/lib/strings/url-helpers'
import {type Shadow} from '#/state/cache/types'
-import {useModalControls} from '#/state/modals'
import {Nux, useNux, useSaveNux} from '#/state/queries/nuxs'
import {
RQKEY as profileQueryKey,
@@ -25,6 +24,7 @@ import {EventStopper} from '#/view/com/util/EventStopper'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button'
import {useDialogControl} from '#/components/Dialog'
+import {UserAddRemoveListsDialog} from '#/components/dialogs/lists/UserAddRemoveListsDialog'
import {StarterPackDialog} from '#/components/dialogs/StarterPackDialog'
import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as ArrowOutOfBoxIcon} from '#/components/icons/ArrowOutOfBox'
import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink'
@@ -74,7 +74,6 @@ let ProfileMenu = ({
const ax = useAnalytics()
const {_} = useLingui()
const {currentAccount, hasSession} = useSession()
- const {openModal} = useModalControls()
const reportDialogControl = useReportDialogControl()
const queryClient = useQueryClient()
const navigation = useNavigation()
@@ -107,6 +106,7 @@ let ProfileMenu = ({
const goLiveDialogControl = useDialogControl()
const goLiveDisabledDialogControl = useDialogControl()
const addToStarterPacksDialogControl = useDialogControl()
+ const addToListsDialogControl = useDialogControl()
const showLoggedOutWarning = useMemo(() => {
return (
@@ -131,15 +131,8 @@ let ProfileMenu = ({
}, [profile])
const onPressAddRemoveLists = useCallback(() => {
- openModal({
- name: 'user-add-remove-lists',
- subject: profile.did,
- handle: profile.handle,
- displayName: profile.displayName || profile.handle,
- onAdd: invalidateProfileQuery,
- onRemove: invalidateProfileQuery,
- })
- }, [profile, openModal, invalidateProfileQuery])
+ addToListsDialogControl.open()
+ }, [addToListsDialogControl])
const onPressMuteAccount = useCallback(async () => {
if (profile.viewer?.muted) {
@@ -530,6 +523,15 @@ let ProfileMenu = ({
targetDid={profile.did}
/>
+
+