diff --git a/src/components/ProfileCard.tsx b/src/components/ProfileCard.tsx
index 394ff99462..1a64c51d5b 100644
--- a/src/components/ProfileCard.tsx
+++ b/src/components/ProfileCard.tsx
@@ -166,29 +166,47 @@ export function NameAndHandle({
profile: bsky.profile.AnyProfileView
moderationOpts: ModerationOpts
}) {
- const t = useTheme()
+ return (
+
+
+
+
+ )
+}
+
+export function Name({
+ profile,
+ moderationOpts,
+}: {
+ profile: bsky.profile.AnyProfileView
+ moderationOpts: ModerationOpts
+}) {
const moderation = moderateProfile(profile, moderationOpts)
const name = sanitizeDisplayName(
profile.displayName || sanitizeHandle(profile.handle),
moderation.ui('displayName'),
)
+ return (
+
+ {name}
+
+ )
+}
+
+export function Handle({profile}: {profile: bsky.profile.AnyProfileView}) {
+ const t = useTheme()
const handle = sanitizeHandle(profile.handle, '@')
return (
-
-
- {name}
-
-
- {handle}
-
-
+
+ {handle}
+
)
}
diff --git a/src/components/dms/dialogs/SearchablePeopleList.tsx b/src/components/dialogs/SearchablePeopleList.tsx
similarity index 78%
rename from src/components/dms/dialogs/SearchablePeopleList.tsx
rename to src/components/dialogs/SearchablePeopleList.tsx
index 05d6f723e1..26e20db57c 100644
--- a/src/components/dms/dialogs/SearchablePeopleList.tsx
+++ b/src/components/dialogs/SearchablePeopleList.tsx
@@ -1,4 +1,5 @@
-import React, {
+import {
+ Fragment,
useCallback,
useLayoutEffect,
useMemo,
@@ -6,7 +7,7 @@ import React, {
useState,
} from 'react'
import {TextInput, View} from 'react-native'
-import {moderateProfile, ModerationOpts} from '@atproto/api'
+import {moderateProfile, type ModerationOpts} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -18,48 +19,62 @@ import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
import {useListConvosQuery} from '#/state/queries/messages/list-conversations'
import {useProfileFollowsQuery} from '#/state/queries/profile-follows'
import {useSession} from '#/state/session'
-import {ListMethods} from '#/view/com/util/List'
-import {UserAvatar} from '#/view/com/util/UserAvatar'
-import {atoms as a, native, useTheme, web} from '#/alf'
+import {type ListMethods} from '#/view/com/util/List'
+import {android, atoms as a, native, useTheme, web} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {canBeMessaged} from '#/components/dms/util'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2'
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
+import * as ProfileCard from '#/components/ProfileCard'
import {Text} from '#/components/Typography'
-import * as bsky from '#/types/bsky'
+import type * as bsky from '#/types/bsky'
-type Item =
- | {
- type: 'profile'
- key: string
- enabled: boolean
- profile: bsky.profile.AnyProfileView
- }
- | {
- type: 'empty'
- key: string
- message: string
- }
- | {
- type: 'placeholder'
- key: string
- }
- | {
- type: 'error'
- key: string
- }
+export type ProfileItem = {
+ type: 'profile'
+ key: string
+ profile: bsky.profile.AnyProfileView
+}
+
+type EmptyItem = {
+ type: 'empty'
+ key: string
+ message: string
+}
+
+type PlaceholderItem = {
+ type: 'placeholder'
+ key: string
+}
+
+type ErrorItem = {
+ type: 'error'
+ key: string
+}
+
+type Item = ProfileItem | EmptyItem | PlaceholderItem | ErrorItem
export function SearchablePeopleList({
title,
- onSelectChat,
showRecentConvos,
+ sortByMessageDeclaration,
+ onSelectChat,
+ renderProfileCard,
}: {
title: string
- onSelectChat: (did: string) => void
showRecentConvos?: boolean
-}) {
+ sortByMessageDeclaration?: boolean
+} & (
+ | {
+ renderProfileCard: (item: ProfileItem) => React.ReactNode
+ onSelectChat?: undefined
+ }
+ | {
+ onSelectChat: (did: string) => void
+ renderProfileCard?: undefined
+ }
+)) {
const t = useTheme()
const {_} = useLingui()
const moderationOpts = useModerationOpts()
@@ -98,15 +113,17 @@ export function SearchablePeopleList({
_items.push({
type: 'profile',
key: profile.did,
- enabled: canBeMessaged(profile),
profile,
})
}
- _items = _items.sort(item => {
- // @ts-ignore
- return item.enabled ? -1 : 1
- })
+ if (sortByMessageDeclaration) {
+ _items = _items.sort(item => {
+ return item.type === 'profile' && canBeMessaged(item.profile)
+ ? -1
+ : 1
+ })
+ }
}
} else {
const placeholders: Item[] = Array(10)
@@ -134,14 +151,13 @@ export function SearchablePeopleList({
_items.push({
type: 'profile',
key: profile.did,
- enabled: true,
profile,
})
}
}
}
- let followsItems: typeof _items = []
+ let followsItems: ProfileItem[] = []
for (const page of follows.pages) {
for (const profile of page.follows) {
@@ -150,17 +166,17 @@ export function SearchablePeopleList({
followsItems.push({
type: 'profile',
key: profile.did,
- enabled: canBeMessaged(profile),
profile,
})
}
}
- // only sort follows
- followsItems = followsItems.sort(item => {
- // @ts-ignore
- return item.enabled ? -1 : 1
- })
+ if (sortByMessageDeclaration) {
+ // only sort follows
+ followsItems = followsItems.sort(item => {
+ return canBeMessaged(item.profile) ? -1 : 1
+ })
+ }
// then append
_items.push(...followsItems)
@@ -173,16 +189,18 @@ export function SearchablePeopleList({
_items.push({
type: 'profile',
key: profile.did,
- enabled: canBeMessaged(profile),
profile,
})
}
}
- _items = _items.sort(item => {
- // @ts-ignore
- return item.enabled ? -1 : 1
- })
+ if (sortByMessageDeclaration) {
+ _items = _items.sort(item => {
+ return item.type === 'profile' && canBeMessaged(item.profile)
+ ? -1
+ : 1
+ })
+ }
} else {
_items.push(...placeholders)
}
@@ -198,6 +216,7 @@ export function SearchablePeopleList({
follows,
convos,
showRecentConvos,
+ sortByMessageDeclaration,
])
if (searchText && !isFetching && !items.length && !isError) {
@@ -208,15 +227,18 @@ export function SearchablePeopleList({
({item}: {item: Item}) => {
switch (item.type) {
case 'profile': {
- return (
-
- )
+ if (renderProfileCard) {
+ return {renderProfileCard(item)}
+ } else {
+ return (
+
+ )
+ }
}
case 'placeholder': {
return
@@ -228,7 +250,7 @@ export function SearchablePeopleList({
return null
}
},
- [moderationOpts, onSelectChat],
+ [moderationOpts, onSelectChat, renderProfileCard],
)
useLayoutEffect(() => {
@@ -247,6 +269,10 @@ export function SearchablePeopleList({
a.relative,
web(a.pt_lg),
native(a.pt_4xl),
+ android({
+ borderTopLeftRadius: a.rounded_md.borderRadius,
+ borderTopRightRadius: a.rounded_md.borderRadius,
+ }),
a.pb_xs,
a.px_lg,
a.border_b,
@@ -327,19 +353,18 @@ export function SearchablePeopleList({
)
}
-function ProfileCard({
- enabled,
+function DefaultProfileCard({
profile,
moderationOpts,
onPress,
}: {
- enabled: boolean
profile: bsky.profile.AnyProfileView
moderationOpts: ModerationOpts
onPress: (did: string) => void
}) {
const t = useTheme()
const {_} = useLingui()
+ const enabled = canBeMessaged(profile)
const moderation = moderateProfile(profile, moderationOpts)
const handle = sanitizeHandle(profile.handle, '@')
const displayName = sanitizeDisplayName(
@@ -360,38 +385,35 @@ function ProfileCard({
-
-
-
- {displayName}
-
-
- {!enabled ? {handle} can't be messaged : handle}
-
-
+
+
+
+
+ {enabled ? (
+
+ ) : (
+
+ {handle} can't be messaged
+
+ )}
+
+
)}
diff --git a/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx b/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx
new file mode 100644
index 0000000000..d975c89ed2
--- /dev/null
+++ b/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx
@@ -0,0 +1,180 @@
+import {useCallback, useMemo} from 'react'
+import {View} from 'react-native'
+import {type AppBskyGraphDefs, type ModerationOpts} from '@atproto/api'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {cleanError} from '#/lib/strings/errors'
+import {useModerationOpts} from '#/state/preferences/moderation-opts'
+import {
+ getMembership,
+ type ListMembersip,
+ useDangerousListMembershipsQuery,
+ useListMembershipAddMutation,
+ useListMembershipRemoveMutation,
+} from '#/state/queries/list-memberships'
+import * as Toast from '#/view/com/util/Toast'
+import {atoms as a} from '#/alf'
+import {Button, ButtonIcon, ButtonText} from '#/components/Button'
+import * as Dialog from '#/components/Dialog'
+import {
+ type ProfileItem,
+ SearchablePeopleList,
+} from '#/components/dialogs/SearchablePeopleList'
+import {Loader} from '#/components/Loader'
+import * as ProfileCard from '#/components/ProfileCard'
+import type * as bsky from '#/types/bsky'
+
+export function ListAddRemoveUsersDialog({
+ control,
+ list,
+ onChange,
+}: {
+ control: Dialog.DialogControlProps
+ list: AppBskyGraphDefs.ListView
+ onChange?: (
+ type: 'add' | 'remove',
+ profile: bsky.profile.AnyProfileView,
+ ) => void | undefined
+}) {
+ return (
+
+
+
+
+ )
+}
+
+function DialogInner({
+ list,
+ onChange,
+}: {
+ list: AppBskyGraphDefs.ListView
+ onChange?: (
+ type: 'add' | 'remove',
+ profile: bsky.profile.AnyProfileView,
+ ) => void | undefined
+}) {
+ const {_} = useLingui()
+ const moderationOpts = useModerationOpts()
+ const {data: memberships} = useDangerousListMembershipsQuery()
+
+ const renderProfileCard = useCallback(
+ (item: ProfileItem) => {
+ return (
+
+ )
+ },
+ [onChange, memberships, list, moderationOpts],
+ )
+
+ return (
+
+ )
+}
+
+function UserResult({
+ profile,
+ list,
+ memberships,
+ onChange,
+ moderationOpts,
+}: {
+ profile: bsky.profile.AnyProfileView
+ list: AppBskyGraphDefs.ListView
+ memberships: ListMembersip[] | undefined
+ onChange?: (
+ type: 'add' | 'remove',
+ profile: bsky.profile.AnyProfileView,
+ ) => void | undefined
+ moderationOpts?: ModerationOpts
+}) {
+ 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', profile)
+ },
+ onError: e => Toast.show(cleanError(e), 'xmark'),
+ })
+ const {mutate: listMembershipRemove, isPending: isRemovingPending} =
+ useListMembershipRemoveMutation({
+ onSuccess: () => {
+ Toast.show(_(msg`Removed from list`))
+ onChange?.('remove', profile)
+ },
+ 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 (
+
+
+
+
+
+
+
+ {membership !== undefined && (
+
+ )}
+
+
+ )
+}
diff --git a/src/components/dms/dialogs/NewChatDialog.tsx b/src/components/dms/dialogs/NewChatDialog.tsx
index c7fedb4888..a5ba793fb3 100644
--- a/src/components/dms/dialogs/NewChatDialog.tsx
+++ b/src/components/dms/dialogs/NewChatDialog.tsx
@@ -11,9 +11,9 @@ import * as Toast from '#/view/com/util/Toast'
import {useTheme} from '#/alf'
import * as Dialog from '#/components/Dialog'
import {useDialogControl} from '#/components/Dialog'
+import {SearchablePeopleList} from '#/components/dialogs/SearchablePeopleList'
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
-import {SearchablePeopleList} from './SearchablePeopleList'
export function NewChat({
control,
@@ -71,6 +71,7 @@ export function NewChat({
diff --git a/src/components/dms/dialogs/ShareViaChatDialog.tsx b/src/components/dms/dialogs/ShareViaChatDialog.tsx
index 4bb27ae698..97897bc286 100644
--- a/src/components/dms/dialogs/ShareViaChatDialog.tsx
+++ b/src/components/dms/dialogs/ShareViaChatDialog.tsx
@@ -7,7 +7,7 @@ import {logger} from '#/logger'
import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members'
import * as Toast from '#/view/com/util/Toast'
import * as Dialog from '#/components/Dialog'
-import {SearchablePeopleList} from './SearchablePeopleList'
+import {SearchablePeopleList} from '#/components/dialogs/SearchablePeopleList'
export function SendViaChatDialog({
control,
@@ -62,6 +62,7 @@ function SendViaChatDialogInner({
title={_(msg`Send post to...`)}
onSelectChat={onCreateChat}
showRecentConvos
+ sortByMessageDeclaration
/>
)
}
diff --git a/src/state/modals/index.tsx b/src/state/modals/index.tsx
index 483de99e49..1709f02882 100644
--- a/src/state/modals/index.tsx
+++ b/src/state/modals/index.tsx
@@ -1,6 +1,6 @@
import React from 'react'
-import {Image as RNImage} from 'react-native-image-crop-picker'
-import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api'
+import {type Image as RNImage} from 'react-native-image-crop-picker'
+import {type AppBskyActorDefs, type AppBskyGraphDefs} from '@atproto/api'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
@@ -26,15 +26,6 @@ export interface UserAddRemoveListsModal {
onRemove?: (listUri: string) => void
}
-export interface ListAddRemoveUsersModal {
- name: 'list-add-remove-users'
- list: AppBskyGraphDefs.ListView
- onChange?: (
- type: 'add' | 'remove',
- profile: AppBskyActorDefs.ProfileViewBasic,
- ) => void
-}
-
export interface CropImageModal {
name: 'crop-image'
uri: string
@@ -107,7 +98,6 @@ export type Modal =
// Lists
| CreateOrEditListModal
| UserAddRemoveListsModal
- | ListAddRemoveUsersModal
// Posts
| CropImageModal
diff --git a/src/state/queries/list-memberships.ts b/src/state/queries/list-memberships.ts
index b93dc059de..410a613ada 100644
--- a/src/state/queries/list-memberships.ts
+++ b/src/state/queries/list-memberships.ts
@@ -90,7 +90,13 @@ export function getMembership(
return membership ? membership.membershipUri : false
}
-export function useListMembershipAddMutation() {
+export function useListMembershipAddMutation({
+ onSuccess,
+ onError,
+}: {
+ onSuccess?: (data: {uri: string; cid: string}) => void
+ onError?: (error: Error) => void
+} = {}) {
const {currentAccount} = useSession()
const agent = useAgent()
const queryClient = useQueryClient()
@@ -117,7 +123,7 @@ export function useListMembershipAddMutation() {
// -prf
return res
},
- onSuccess(data, variables) {
+ onSuccess: (data, variables) => {
// manually update the cache; a refetch is too expensive
let memberships = queryClient.getQueryData(RQKEY())
if (memberships) {
@@ -145,11 +151,19 @@ export function useListMembershipAddMutation() {
queryKey: LIST_MEMBERS_RQKEY(variables.listUri),
})
}, 1e3)
+ onSuccess?.(data)
},
+ onError,
})
}
-export function useListMembershipRemoveMutation() {
+export function useListMembershipRemoveMutation({
+ onSuccess,
+ onError,
+}: {
+ onSuccess?: (data: void) => void
+ onError?: (error: Error) => void
+} = {}) {
const {currentAccount} = useSession()
const agent = useAgent()
const queryClient = useQueryClient()
@@ -172,7 +186,7 @@ export function useListMembershipRemoveMutation() {
// query for that, so we use a timeout below
// -prf
},
- onSuccess(data, variables) {
+ onSuccess: (data, variables) => {
// manually update the cache; a refetch is too expensive
let memberships = queryClient.getQueryData(RQKEY())
if (memberships) {
@@ -192,6 +206,8 @@ export function useListMembershipRemoveMutation() {
queryKey: LIST_MEMBERS_RQKEY(variables.listUri),
})
}, 1e3)
+ onSuccess?.(data)
},
+ onError,
})
}
diff --git a/src/view/com/modals/ListAddRemoveUsers.tsx b/src/view/com/modals/ListAddRemoveUsers.tsx
deleted file mode 100644
index 5285d4a157..0000000000
--- a/src/view/com/modals/ListAddRemoveUsers.tsx
+++ /dev/null
@@ -1,316 +0,0 @@
-import React, {useCallback, useState} from 'react'
-import {
- ActivityIndicator,
- Pressable,
- SafeAreaView,
- StyleSheet,
- View,
-} from 'react-native'
-import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
-import {msg, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {HITSLOP_20} from '#/lib/constants'
-import {useIsKeyboardVisible} from '#/lib/hooks/useIsKeyboardVisible'
-import {usePalette} from '#/lib/hooks/usePalette'
-import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
-import {sanitizeDisplayName} from '#/lib/strings/display-names'
-import {cleanError} from '#/lib/strings/errors'
-import {sanitizeHandle} from '#/lib/strings/handles'
-import {colors, s} from '#/lib/styles'
-import {isWeb} from '#/platform/detection'
-import {useModalControls} from '#/state/modals'
-import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
-import {
- getMembership,
- ListMembersip,
- useDangerousListMembershipsQuery,
- useListMembershipAddMutation,
- useListMembershipRemoveMutation,
-} from '#/state/queries/list-memberships'
-import {Button} from '../util/forms/Button'
-import {Text} from '../util/text/Text'
-import * as Toast from '../util/Toast'
-import {UserAvatar} from '../util/UserAvatar'
-import {ScrollView, TextInput} from './util'
-
-export const snapPoints = ['90%']
-
-export function Component({
- list,
- onChange,
-}: {
- list: AppBskyGraphDefs.ListView
- onChange?: (
- type: 'add' | 'remove',
- profile: AppBskyActorDefs.ProfileViewBasic,
- ) => void
-}) {
- const pal = usePalette('default')
- const {_} = useLingui()
- const {closeModal} = useModalControls()
- const {isMobile} = useWebMediaQueries()
- const [query, setQuery] = useState('')
- const autocomplete = useActorAutocompleteQuery(query)
- const {data: memberships} = useDangerousListMembershipsQuery()
- const [isKeyboardVisible] = useIsKeyboardVisible()
-
- const onPressCancelSearch = useCallback(() => setQuery(''), [setQuery])
-
- return (
-
-
-
-
-
- {query ? (
-
-
-
- ) : undefined}
-
-
- {autocomplete.isLoading ? (
-
-
-
- ) : autocomplete.data?.length ? (
- <>
- {autocomplete.data.slice(0, 40).map((item, i) => (
-
- ))}
- >
- ) : (
-
- No results found for {query}
-
- )}
-
-
-
-
-
- )
-}
-
-function UserResult({
- profile,
- list,
- memberships,
- noBorder,
- onChange,
-}: {
- profile: AppBskyActorDefs.ProfileViewBasic
- list: AppBskyGraphDefs.ListView
- memberships: ListMembersip[] | undefined
- noBorder: boolean
- onChange?: (
- type: 'add' | 'remove',
- profile: AppBskyActorDefs.ProfileViewBasic,
- ) => void | undefined
-}) {
- const pal = usePalette('default')
- const {_} = useLingui()
- const [isProcessing, setIsProcessing] = useState(false)
- const membership = React.useMemo(
- () => getMembership(memberships, list.uri, profile.did),
- [memberships, list.uri, profile.did],
- )
- 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: profile.did,
- })
- Toast.show(_(msg`Added to list`))
- onChange?.('add', profile)
- } else {
- await listMembershipRemoveMutation.mutateAsync({
- listUri: list.uri,
- actorDid: profile.did,
- membershipUri: membership,
- })
- Toast.show(_(msg`Removed from list`))
- onChange?.('remove', profile)
- }
- } catch (e) {
- Toast.show(cleanError(e), 'xmark')
- } finally {
- setIsProcessing(false)
- }
- }, [
- _,
- list,
- profile,
- membership,
- setIsProcessing,
- onChange,
- listMembershipAddMutation,
- listMembershipRemoveMutation,
- ])
-
- return (
-
-
-
-
-
-
- {sanitizeDisplayName(
- profile.displayName || sanitizeHandle(profile.handle),
- )}
-
-
- {sanitizeHandle(profile.handle, '@')}
-
- {!!profile.viewer?.followedBy && }
-
-
- {isProcessing || typeof membership === 'undefined' ? (
-
- ) : (
-
- )}
-
-
- )
-}
-
-const styles = StyleSheet.create({
- fixedHeight: {
- // @ts-ignore web only -prf
- height: '80vh',
- },
- titleSection: {
- paddingTop: isWeb ? 0 : 4,
- paddingBottom: isWeb ? 14 : 10,
- },
- title: {
- textAlign: 'center',
- fontWeight: '600',
- marginBottom: 5,
- },
- searchContainer: {
- flexDirection: 'row',
- alignItems: 'center',
- gap: 8,
- borderWidth: 1,
- borderRadius: 24,
- paddingHorizontal: 16,
- paddingVertical: 10,
- },
- searchInput: {
- fontSize: 16,
- flex: 1,
- },
- btn: {
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'center',
- borderRadius: 32,
- padding: 14,
- backgroundColor: colors.blue3,
- },
- btnContainer: {
- paddingTop: 10,
- },
-})
diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx
index 9ad651b4fe..b4572172cf 100644
--- a/src/view/com/modals/Modal.tsx
+++ b/src/view/com/modals/Modal.tsx
@@ -17,7 +17,6 @@ import * as InviteCodesModal from './InviteCodes'
import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings'
import * as LinkWarningModal from './LinkWarning'
-import * as ListAddUserModal from './ListAddRemoveUsers'
import * as UserAddRemoveListsModal from './UserAddRemoveLists'
import * as VerifyEmailModal from './VerifyEmail'
@@ -61,9 +60,6 @@ export function ModalsContainer() {
} else if (activeModal?.name === 'user-add-remove-lists') {
snapPoints = UserAddRemoveListsModal.snapPoints
element =
- } else if (activeModal?.name === 'list-add-remove-users') {
- snapPoints = ListAddUserModal.snapPoints
- element =
} else if (activeModal?.name === 'delete-account') {
snapPoints = DeleteAccountModal.snapPoints
element =
diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx
index 0c49c87716..74ee7c210d 100644
--- a/src/view/com/modals/Modal.web.tsx
+++ b/src/view/com/modals/Modal.web.tsx
@@ -4,7 +4,7 @@ import {RemoveScrollBar} from 'react-remove-scroll-bar'
import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
-import type {Modal as ModalIface} from '#/state/modals'
+import {type Modal as ModalIface} from '#/state/modals'
import {useModalControls, useModals} from '#/state/modals'
import * as ChangeEmailModal from './ChangeEmail'
import * as ChangePasswordModal from './ChangePassword'
@@ -16,7 +16,6 @@ import * as InviteCodesModal from './InviteCodes'
import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings'
import * as LinkWarningModal from './LinkWarning'
-import * as ListAddUserModal from './ListAddRemoveUsers'
import * as UserAddRemoveLists from './UserAddRemoveLists'
import * as VerifyEmailModal from './VerifyEmail'
@@ -65,8 +64,6 @@ function Modal({modal}: {modal: ModalIface}) {
element =
} else if (modal.name === 'user-add-remove-lists') {
element =
- } else if (modal.name === 'list-add-remove-users') {
- element =
} else if (modal.name === 'crop-image') {
element =
} else if (modal.name === 'delete-account') {
diff --git a/src/view/screens/ProfileList.tsx b/src/view/screens/ProfileList.tsx
index 966534d978..61f1eb745a 100644
--- a/src/view/screens/ProfileList.tsx
+++ b/src/view/screens/ProfileList.tsx
@@ -5,7 +5,7 @@ import {
AppBskyGraphDefs,
AtUri,
moderateUserList,
- ModerationOpts,
+ type ModerationOpts,
RichText as RichTextAPI,
} from '@atproto/api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
@@ -21,8 +21,11 @@ import {useSetTitle} from '#/lib/hooks/useSetTitle'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {ComposeIcon2} from '#/lib/icons'
import {makeListLink} from '#/lib/routes/links'
-import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
-import {NavigationProp} from '#/lib/routes/types'
+import {
+ type CommonNavigatorParams,
+ type NativeStackScreenProps,
+} from '#/lib/routes/types'
+import {type NavigationProp} from '#/lib/routes/types'
import {shareUrl} from '#/lib/sharing'
import {cleanError} from '#/lib/strings/errors'
import {toShareUrl} from '#/lib/strings/url-helpers'
@@ -38,12 +41,12 @@ import {
useListMuteMutation,
useListQuery,
} from '#/state/queries/list'
-import {FeedDescriptor} from '#/state/queries/post-feed'
+import {type FeedDescriptor} from '#/state/queries/post-feed'
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
import {
useAddSavedFeedsMutation,
usePreferencesQuery,
- UsePreferencesQueryResponse,
+ type UsePreferencesQueryResponse,
useRemoveFeedMutation,
useUpdateSavedFeedsMutation,
} from '#/state/queries/preferences'
@@ -60,10 +63,10 @@ import {EmptyState} from '#/view/com/util/EmptyState'
import {FAB} from '#/view/com/util/fab/FAB'
import {Button} from '#/view/com/util/forms/Button'
import {
- DropdownItem,
+ type DropdownItem,
NativeDropdown,
} from '#/view/com/util/forms/NativeDropdown'
-import {ListRef} from '#/view/com/util/List'
+import {type ListRef} from '#/view/com/util/List'
import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
import {LoadingScreen} from '#/view/com/util/LoadingScreen'
import {Text} from '#/view/com/util/text/Text'
@@ -72,6 +75,7 @@ import {ListHiddenScreen} from '#/screens/List/ListHiddenScreen'
import {atoms as a} from '#/alf'
import {Button as NewButton, ButtonIcon, ButtonText} from '#/components/Button'
import {useDialogControl} from '#/components/Dialog'
+import {ListAddRemoveUsersDialog} from '#/components/dialogs/lists/ListAddRemoveUsersDialog'
import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person'
import * as Layout from '#/components/Layout'
import * as Hider from '#/components/moderation/Hider'
@@ -157,12 +161,12 @@ function ProfileListScreenLoaded({
const {rkey} = route.params
const feedSectionRef = React.useRef(null)
const aboutSectionRef = React.useRef(null)
- const {openModal} = useModalControls()
const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST
const isScreenFocused = useIsFocused()
const isHidden = list.labels?.findIndex(l => l.val === '!hide') !== -1
const isOwner = currentAccount?.did === list.creator.did
const scrollElRef = useAnimatedRef()
+ const addUserDialogControl = useDialogControl()
const sectionTitlesCurate = [_(msg`Posts`), _(msg`People`)]
const moderation = React.useMemo(() => {
@@ -177,17 +181,11 @@ function ProfileListScreenLoaded({
}, [setMinimalShellMode]),
)
- const onPressAddUser = useCallback(() => {
- openModal({
- name: 'list-add-remove-users',
- list,
- onChange() {
- if (isCurateList) {
- truncateAndInvalidate(queryClient, FEED_RQKEY(`list|${list.uri}`))
- }
- },
- })
- }, [openModal, list, isCurateList, queryClient])
+ const onChangeMembers = useCallback(() => {
+ if (isCurateList) {
+ truncateAndInvalidate(queryClient, FEED_RQKEY(`list|${list.uri}`))
+ }
+ }, [list.uri, isCurateList, queryClient])
const onCurrentPageSelected = React.useCallback(
(index: number) => {
@@ -225,7 +223,7 @@ function ProfileListScreenLoaded({
headerHeight={headerHeight}
isFocused={isScreenFocused && isFocused}
isOwner={isOwner}
- onPressAddUser={onPressAddUser}
+ onPressAddUser={addUserDialogControl.open}
/>
)}
{({headerHeight, scrollElRef}) => (
@@ -233,7 +231,7 @@ function ProfileListScreenLoaded({
ref={aboutSectionRef}
scrollElRef={scrollElRef as ListRef}
list={list}
- onPressAddUser={onPressAddUser}
+ onPressAddUser={addUserDialogControl.open}
headerHeight={headerHeight}
/>
)}
@@ -253,6 +251,11 @@ function ProfileListScreenLoaded({
accessibilityHint=""
/>
+
)
@@ -268,7 +271,7 @@ function ProfileListScreenLoaded({
+
)