Enable sharing a post to a new group chat (#11191)
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
import {memo, useMemo} from 'react'
|
||||
import * as ExpoClipboard from 'expo-clipboard'
|
||||
import {AtUri} from '@atproto/api'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans} from '@lingui/react/macro'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
@@ -37,7 +35,7 @@ let ShareMenuItems = ({
|
||||
}: ShareMenuItemsProps): React.ReactNode => {
|
||||
const ax = useAnalytics()
|
||||
const {hasSession} = useSession()
|
||||
const {_} = useLingui()
|
||||
const {t: l} = useLingui()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const sendViaChatControl = useDialogControl()
|
||||
const [devModeEnabled] = useDevMode()
|
||||
@@ -61,7 +59,7 @@ let ShareMenuItems = ({
|
||||
const onSharePost = () => {
|
||||
ax.metric('share:press:nativeShare', {})
|
||||
const url = toShareUrl(href)
|
||||
shareUrl(url)
|
||||
void shareUrl(url)
|
||||
onShareProp()
|
||||
}
|
||||
|
||||
@@ -74,7 +72,7 @@ let ShareMenuItems = ({
|
||||
} else {
|
||||
await ExpoClipboard.setStringAsync(url)
|
||||
}
|
||||
Toast.show(_(msg`Copied to clipboard`), {
|
||||
Toast.show(l`Copied to clipboard`, {
|
||||
type: 'success',
|
||||
})
|
||||
onShareProp()
|
||||
@@ -93,11 +91,11 @@ let ShareMenuItems = ({
|
||||
}
|
||||
|
||||
const onShareATURI = () => {
|
||||
shareText(postUri)
|
||||
void shareText(postUri)
|
||||
}
|
||||
|
||||
const onShareAuthorDID = () => {
|
||||
shareText(postAuthor.did)
|
||||
void shareText(postAuthor.did)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -113,13 +111,13 @@ let ShareMenuItems = ({
|
||||
</Menu.ContainerItem>
|
||||
<Menu.Item
|
||||
testID="postDropdownSendViaDMBtn"
|
||||
label={_(msg`Send via direct message`)}
|
||||
label={l`Send via chat`}
|
||||
onPress={() => {
|
||||
ax.metric('share:press:openDmSearch', {})
|
||||
sendViaChatControl.open()
|
||||
}}>
|
||||
<Menu.ItemText>
|
||||
<Trans>Send via direct message</Trans>
|
||||
<Trans>Send via chat</Trans>
|
||||
</Menu.ItemText>
|
||||
<Menu.ItemIcon icon={PaperPlaneIcon} position="right" />
|
||||
</Menu.Item>
|
||||
@@ -129,7 +127,7 @@ let ShareMenuItems = ({
|
||||
<Menu.Group>
|
||||
<Menu.Item
|
||||
testID="postDropdownShareBtn"
|
||||
label={_(msg`Share via...`)}
|
||||
label={l`Share via...`}
|
||||
onPress={onSharePost}>
|
||||
<Menu.ItemText>
|
||||
<Trans>Share via...</Trans>
|
||||
@@ -139,8 +137,8 @@ let ShareMenuItems = ({
|
||||
|
||||
<Menu.Item
|
||||
testID="postDropdownShareBtn"
|
||||
label={_(msg`Copy link to post`)}
|
||||
onPress={onCopyLink}>
|
||||
label={l`Copy link to post`}
|
||||
onPress={() => void onCopyLink()}>
|
||||
<Menu.ItemText>
|
||||
<Trans>Copy link to post</Trans>
|
||||
</Menu.ItemText>
|
||||
@@ -164,7 +162,7 @@ let ShareMenuItems = ({
|
||||
<Menu.Group>
|
||||
<Menu.Item
|
||||
testID="postAtUriShareBtn"
|
||||
label={_(msg`Share post at:// URI`)}
|
||||
label={l`Share post at:// URI`}
|
||||
onPress={onShareATURI}>
|
||||
<Menu.ItemText>
|
||||
<Trans>Share post at:// URI</Trans>
|
||||
@@ -173,7 +171,7 @@ let ShareMenuItems = ({
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
testID="postAuthorDIDShareBtn"
|
||||
label={_(msg`Share author DID`)}
|
||||
label={l`Share author DID`}
|
||||
onPress={onShareAuthorDID}>
|
||||
<Menu.ItemText>
|
||||
<Trans>Share author DID</Trans>
|
||||
@@ -183,7 +181,6 @@ let ShareMenuItems = ({
|
||||
</Menu.Group>
|
||||
)}
|
||||
</Menu.Outer>
|
||||
|
||||
<SendViaChatDialog
|
||||
control={sendViaChatControl}
|
||||
onSelectChat={onSelectChatToShareTo}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import {memo, useMemo} from 'react'
|
||||
import {AtUri} from '@atproto/api'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans} from '@lingui/react/macro'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
@@ -35,7 +33,7 @@ let ShareMenuItems = ({
|
||||
const ax = useAnalytics()
|
||||
const {hasSession} = useSession()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const {_} = useLingui()
|
||||
const {t: l} = useLingui()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const embedPostControl = useDialogControl()
|
||||
const sendViaChatControl = useDialogControl()
|
||||
@@ -60,7 +58,7 @@ let ShareMenuItems = ({
|
||||
const onCopyLink = () => {
|
||||
ax.metric('share:press:copyLink', {})
|
||||
const url = toShareUrl(href)
|
||||
shareUrl(url)
|
||||
void shareUrl(url)
|
||||
onShareProp()
|
||||
}
|
||||
|
||||
@@ -75,17 +73,17 @@ let ShareMenuItems = ({
|
||||
const canEmbed = IS_WEB && gtMobile && !hideInPWI
|
||||
|
||||
const onShareATURI = () => {
|
||||
shareText(postUri)
|
||||
void shareText(postUri)
|
||||
}
|
||||
|
||||
const onShareAuthorDID = () => {
|
||||
shareText(postAuthor.did)
|
||||
void shareText(postAuthor.did)
|
||||
}
|
||||
|
||||
const copyLinkItem = (
|
||||
<Menu.Item
|
||||
testID="postDropdownShareBtn"
|
||||
label={_(msg`Copy link to post`)}
|
||||
label={l`Copy link to post`}
|
||||
onPress={onCopyLink}>
|
||||
<Menu.ItemText>
|
||||
<Trans>Copy link to post</Trans>
|
||||
@@ -102,13 +100,13 @@ let ShareMenuItems = ({
|
||||
{hasSession && aa.state.access === aa.Access.Full && (
|
||||
<Menu.Item
|
||||
testID="postDropdownSendViaDMBtn"
|
||||
label={_(msg`Send via direct message`)}
|
||||
label={l`Send via chat`}
|
||||
onPress={() => {
|
||||
ax.metric('share:press:openDmSearch', {})
|
||||
sendViaChatControl.open()
|
||||
}}>
|
||||
<Menu.ItemText>
|
||||
<Trans>Send via direct message</Trans>
|
||||
<Trans>Send via chat</Trans>
|
||||
</Menu.ItemText>
|
||||
<Menu.ItemIcon icon={Send} position="right" />
|
||||
</Menu.Item>
|
||||
@@ -117,12 +115,12 @@ let ShareMenuItems = ({
|
||||
{canEmbed && (
|
||||
<Menu.Item
|
||||
testID="postDropdownEmbedBtn"
|
||||
label={_(msg`Embed post`)}
|
||||
label={l`Embed post`}
|
||||
onPress={() => {
|
||||
ax.metric('share:press:embed', {})
|
||||
embedPostControl.open()
|
||||
}}>
|
||||
<Menu.ItemText>{_(msg`Embed post`)}</Menu.ItemText>
|
||||
<Menu.ItemText>{l`Embed post`}</Menu.ItemText>
|
||||
<Menu.ItemIcon icon={CodeBracketsIcon} position="right" />
|
||||
</Menu.Item>
|
||||
)}
|
||||
@@ -142,7 +140,7 @@ let ShareMenuItems = ({
|
||||
<Menu.Divider />
|
||||
<Menu.Item
|
||||
testID="postAtUriShareBtn"
|
||||
label={_(msg`Copy post at:// URI`)}
|
||||
label={l`Copy post at:// URI`}
|
||||
onPress={onShareATURI}>
|
||||
<Menu.ItemText>
|
||||
<Trans>Copy post at:// URI</Trans>
|
||||
@@ -151,7 +149,7 @@ let ShareMenuItems = ({
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
testID="postAuthorDIDShareBtn"
|
||||
label={_(msg`Copy author DID`)}
|
||||
label={l`Copy author DID`}
|
||||
onPress={onShareAuthorDID}>
|
||||
<Menu.ItemText>
|
||||
<Trans>Copy author DID</Trans>
|
||||
@@ -161,7 +159,6 @@ let ShareMenuItems = ({
|
||||
</>
|
||||
)}
|
||||
</Menu.Outer>
|
||||
|
||||
{canEmbed && (
|
||||
<EmbedDialog
|
||||
control={embedPostControl}
|
||||
@@ -172,7 +169,6 @@ let ShareMenuItems = ({
|
||||
timestamp={timestamp}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SendViaChatDialog
|
||||
control={sendViaChatControl}
|
||||
onSelectChat={onSelectChatToShareTo}
|
||||
|
||||
@@ -11,19 +11,33 @@ import {moderateProfile, type ModerationOpts} from '@atproto/api'
|
||||
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {MAX_GROUP_NAME_GRAPHEME_LENGTH} from '#/lib/constants'
|
||||
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {isOverMaxGraphemeCount} from '#/lib/strings/helpers'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
|
||||
import {useChatActorStatusQuery} from '#/state/queries/messages/get-status'
|
||||
import {useListConvosQuery} from '#/state/queries/messages/list-conversations'
|
||||
import {useProfileFollowsQuery} from '#/state/queries/profile-follows'
|
||||
import {useSession} from '#/state/session'
|
||||
import {type ListMethods} from '#/view/com/util/List'
|
||||
import {android, atoms as a, native, useTheme, web} from '#/alf'
|
||||
import {AvatarBubbles} from '#/components/AvatarBubbles'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {canBeAddedToGroup, canBeMessaged} from '#/components/dms/util'
|
||||
import {ChatProfileTabs} from '#/components/dms/ChatProfileTabs'
|
||||
import {EmptyMemberList} from '#/components/dms/components/EmptyMemberList'
|
||||
import {GroupChatProfileCard} from '#/components/dms/components/GroupChatProfileCard'
|
||||
import {ProfileCardSkeleton} from '#/components/dms/components/ProfileCardSkeleton'
|
||||
import {UserLabel} from '#/components/dms/components/UserLabel'
|
||||
import {UserSearchInput} from '#/components/dms/components/UserSearchInput'
|
||||
import {
|
||||
canBeAddedToGroup,
|
||||
canBeMessaged,
|
||||
type ConvoWithDetails,
|
||||
parseConvoView,
|
||||
} from '#/components/dms/util'
|
||||
import * as TextField from '#/components/forms/TextField'
|
||||
import * as Toggle from '#/components/forms/Toggle'
|
||||
import {
|
||||
@@ -33,18 +47,13 @@ import {
|
||||
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRightIcon} from '#/components/icons/Chevron'
|
||||
import {PersonGroup_Stroke2_Corner2_Rounded as PersonGroupIcon} from '#/components/icons/Person'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
|
||||
import {ProfileBadges} from '#/components/ProfileBadges'
|
||||
import * as ProfileCard from '#/components/ProfileCard'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useAgeAssurance} from '#/ageAssurance'
|
||||
import {IS_NATIVE, IS_WEB} from '#/env'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
import {ChatProfileTabs} from './ChatProfileTabs'
|
||||
import {EmptyMemberList} from './components/EmptyMemberList'
|
||||
import {GroupChatProfileCard} from './components/GroupChatProfileCard'
|
||||
import {ProfileCardSkeleton} from './components/ProfileCardSkeleton'
|
||||
import {UserLabel} from './components/UserLabel'
|
||||
import {UserSearchInput} from './components/UserSearchInput'
|
||||
|
||||
type NewGroupChatItem = {
|
||||
type: 'newGroupChat'
|
||||
@@ -63,6 +72,12 @@ type ProfileItem = {
|
||||
profile: bsky.profile.AnyProfileView
|
||||
}
|
||||
|
||||
type ExistingChatItem = {
|
||||
type: 'existingChat'
|
||||
key: string
|
||||
convo: ConvoWithDetails
|
||||
}
|
||||
|
||||
type EmptyItem = {
|
||||
type: 'empty'
|
||||
key: string
|
||||
@@ -83,6 +98,7 @@ type Item =
|
||||
| NewGroupChatItem
|
||||
| LabelItem
|
||||
| ProfileItem
|
||||
| ExistingChatItem
|
||||
| EmptyItem
|
||||
| PlaceholderItem
|
||||
| ErrorItem
|
||||
@@ -212,11 +228,17 @@ export function InitiateChatFlow({
|
||||
onSelectChat,
|
||||
onSelectGroupChat,
|
||||
startInGroupChat = false,
|
||||
showRecentConvos = false,
|
||||
onSelectExistingChat,
|
||||
sortByMessageDeclaration = false,
|
||||
}: {
|
||||
title: string
|
||||
onSelectChat: (did: string) => void
|
||||
onSelectGroupChat: (dids: string[], groupName: string) => void
|
||||
startInGroupChat?: boolean
|
||||
showRecentConvos?: boolean
|
||||
onSelectExistingChat?: (convoId: string) => void
|
||||
sortByMessageDeclaration?: boolean
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {t: l} = useLingui()
|
||||
@@ -230,6 +252,12 @@ export function InitiateChatFlow({
|
||||
const inputRef = useRef<TextInput>(null)
|
||||
const accountTooNewPromptControl = Dialog.useDialogControl()
|
||||
|
||||
const {data: convos} = useListConvosQuery({
|
||||
enabled: showRecentConvos,
|
||||
status: 'accepted',
|
||||
lockStatus: 'unlocked',
|
||||
})
|
||||
|
||||
const {data: chatStatus} = useChatActorStatusQuery()
|
||||
const canCreateGroups = chatStatus?.canCreateGroups ?? true
|
||||
const groupMemberLimit = chatStatus?.groupMemberLimit
|
||||
@@ -281,6 +309,10 @@ export function InitiateChatFlow({
|
||||
let _items: Item[] = []
|
||||
const checker =
|
||||
chatState === ChatState.NEW_GROUP_CHAT ? canBeAddedToGroup : canBeMessaged
|
||||
const messageDeclarationRank = (item: Item) =>
|
||||
item.type === 'profile' && checker(item.profile) ? 0 : 1
|
||||
const compareByMessageDeclaration = (a: Item, b: Item) =>
|
||||
messageDeclarationRank(a) - messageDeclarationRank(b)
|
||||
|
||||
if (isError) {
|
||||
_items.push({
|
||||
@@ -310,9 +342,9 @@ export function InitiateChatFlow({
|
||||
})
|
||||
}
|
||||
|
||||
_items = _items.sort(item => {
|
||||
return item.type === 'profile' && checker(item.profile) ? -1 : 1
|
||||
})
|
||||
if (sortByMessageDeclaration) {
|
||||
_items = _items.sort(compareByMessageDeclaration)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const placeholders: Item[] = Array(10)
|
||||
@@ -322,7 +354,57 @@ export function InitiateChatFlow({
|
||||
key: i + '',
|
||||
}))
|
||||
|
||||
if (follows) {
|
||||
if (
|
||||
chatState === ChatState.NEW_CHAT &&
|
||||
showRecentConvos &&
|
||||
convos &&
|
||||
follows
|
||||
) {
|
||||
const usedDids = new Set()
|
||||
|
||||
for (const page of convos.pages) {
|
||||
for (const convoView of page.convos) {
|
||||
const convo = parseConvoView(convoView, currentAccount?.did)
|
||||
|
||||
if (!convo) continue
|
||||
|
||||
if (convo.kind === 'group') {
|
||||
_items.push({
|
||||
type: 'existingChat',
|
||||
key: convo.view.id,
|
||||
convo,
|
||||
})
|
||||
} else {
|
||||
if (convo.primaryMember.handle === 'missing.invalid') continue
|
||||
if (usedDids.has(convo.primaryMember.did)) continue
|
||||
|
||||
usedDids.add(convo.primaryMember.did)
|
||||
|
||||
_items.push({
|
||||
type: 'existingChat',
|
||||
key: convo.view.id,
|
||||
convo,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let followsItems: ProfileItem[] = []
|
||||
|
||||
for (const page of follows.pages) {
|
||||
for (const profile of page.follows) {
|
||||
if (usedDids.has(profile.did)) continue
|
||||
if (!checker(profile)) continue
|
||||
followsItems.push({
|
||||
type: 'profile',
|
||||
key: profile.did,
|
||||
profile,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
_items.push(...followsItems)
|
||||
} else if (follows) {
|
||||
for (const page of follows.pages) {
|
||||
for (const profile of page.follows) {
|
||||
if (!checker(profile)) continue
|
||||
@@ -359,10 +441,19 @@ export function InitiateChatFlow({
|
||||
_items.unshift({type: 'newGroupChat', key: 'newGroupChat'})
|
||||
}
|
||||
|
||||
return _items
|
||||
const profileDids = new Set<string>()
|
||||
|
||||
return _items.filter(item => {
|
||||
if (item.type !== 'profile') return true
|
||||
if (profileDids.has(item.profile.did)) return false
|
||||
|
||||
profileDids.add(item.profile.did)
|
||||
return true
|
||||
})
|
||||
}, [
|
||||
isError,
|
||||
chatState,
|
||||
convos,
|
||||
searchText,
|
||||
l,
|
||||
groupChatProfiles,
|
||||
@@ -370,6 +461,8 @@ export function InitiateChatFlow({
|
||||
currentAccount?.did,
|
||||
follows,
|
||||
aa.flags.groupChatDisabled,
|
||||
showRecentConvos,
|
||||
sortByMessageDeclaration,
|
||||
])
|
||||
|
||||
if (searchText && !isFetching && !items.length && !isError) {
|
||||
@@ -429,6 +522,16 @@ export function InitiateChatFlow({
|
||||
case 'label': {
|
||||
return <UserLabel key={item.key} message={item.message} />
|
||||
}
|
||||
case 'existingChat': {
|
||||
return showRecentConvos && onSelectExistingChat ? (
|
||||
<ExistingChatCard
|
||||
key={item.key}
|
||||
convo={item.convo}
|
||||
moderationOpts={moderationOpts!}
|
||||
onPress={onSelectExistingChat}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
case 'profile': {
|
||||
switch (chatState) {
|
||||
case ChatState.NEW_CHAT:
|
||||
@@ -474,6 +577,8 @@ export function InitiateChatFlow({
|
||||
handlePressNewGroupChat,
|
||||
moderationOpts,
|
||||
onSelectChat,
|
||||
onSelectExistingChat,
|
||||
showRecentConvos,
|
||||
],
|
||||
)
|
||||
|
||||
@@ -845,6 +950,114 @@ function NewGroupChatButton({
|
||||
)
|
||||
}
|
||||
|
||||
function ExistingChatCard({
|
||||
convo,
|
||||
moderationOpts,
|
||||
onPress,
|
||||
}: {
|
||||
convo: ConvoWithDetails
|
||||
moderationOpts: ModerationOpts
|
||||
onPress: (convoId: string) => void
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {t: l} = useLingui()
|
||||
const enabled =
|
||||
convo.kind === 'group' ? convo.details.lockStatus === 'unlocked' : true
|
||||
const name =
|
||||
convo.kind === 'group'
|
||||
? convo.details.name
|
||||
: createSanitizedDisplayName(
|
||||
convo.primaryMember,
|
||||
true,
|
||||
moderateProfile(convo.primaryMember, moderationOpts).ui(
|
||||
'displayName',
|
||||
),
|
||||
)
|
||||
|
||||
const handleOnPress = useCallback(() => {
|
||||
onPress(convo.view.id)
|
||||
}, [onPress, convo.view.id])
|
||||
|
||||
return (
|
||||
<Button
|
||||
disabled={!enabled}
|
||||
label={l`Select chat "${name}"`}
|
||||
onPress={handleOnPress}>
|
||||
{({hovered, pressed, focused}) => (
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.py_sm,
|
||||
a.px_lg,
|
||||
!enabled
|
||||
? {opacity: 0.5}
|
||||
: pressed || focused || hovered
|
||||
? t.atoms.bg_contrast_25
|
||||
: t.atoms.bg,
|
||||
]}>
|
||||
<ProfileCard.Header>
|
||||
{convo.kind === 'group' ? (
|
||||
<AvatarBubbles profiles={convo.members} size={40} />
|
||||
) : (
|
||||
<ProfileCard.Avatar
|
||||
profile={convo.primaryMember}
|
||||
moderationOpts={moderationOpts}
|
||||
disabledPreview
|
||||
/>
|
||||
)}
|
||||
<View style={[a.flex_1]}>
|
||||
<View style={[a.flex_row, a.align_center, a.max_w_full]}>
|
||||
<Text
|
||||
emoji
|
||||
style={[
|
||||
a.text_md,
|
||||
a.font_semi_bold,
|
||||
a.leading_snug,
|
||||
a.self_start,
|
||||
a.flex_shrink,
|
||||
]}
|
||||
numberOfLines={1}>
|
||||
{name}
|
||||
</Text>
|
||||
{convo.kind === 'direct' && (
|
||||
<ProfileBadges
|
||||
profile={convo.primaryMember}
|
||||
size="md"
|
||||
style={[a.pl_xs]}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
{convo.kind === 'direct' ? (
|
||||
<ProfileCard.Handle profile={convo.primaryMember} />
|
||||
) : (
|
||||
<>
|
||||
{enabled ? (
|
||||
<Text
|
||||
style={[a.leading_snug, t.atoms.text_contrast_medium]}
|
||||
numberOfLines={2}>
|
||||
<Plural
|
||||
value={convo.details.memberCount}
|
||||
one="# member"
|
||||
other="# members"
|
||||
/>
|
||||
</Text>
|
||||
) : (
|
||||
<Text
|
||||
style={[a.leading_snug, t.atoms.text_contrast_high]}
|
||||
numberOfLines={2}>
|
||||
<Trans>Group is locked</Trans>
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</ProfileCard.Header>
|
||||
</View>
|
||||
)}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
function DefaultProfileCard({
|
||||
profile,
|
||||
moderationOpts,
|
||||
|
||||
@@ -89,7 +89,7 @@ export function NewChat({
|
||||
},
|
||||
onError: error => {
|
||||
logger.error('Failed to create groupchat', {safeMessage: error})
|
||||
let errorMessage = l`An issue occurred creating the group chat, please try again.`
|
||||
let errorMessage = l`An issue occurred starting the group chat, please try again.`
|
||||
if (isNetworkError(error)) {
|
||||
errorMessage = l`A network error occurred. Please check your internet connection.`
|
||||
} else if (
|
||||
@@ -184,6 +184,7 @@ export function NewChat({
|
||||
title={l`New chat`}
|
||||
onSelectChat={onCreateChat}
|
||||
onSelectGroupChat={onCreateGroupChat}
|
||||
sortByMessageDeclaration
|
||||
startInGroupChat={startInGroupChat}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import {useCallback} from 'react'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useCallback, useState} from 'react'
|
||||
import {
|
||||
ChatBskyConvoGetConvoForMembers,
|
||||
ChatBskyGroupCreateGroup,
|
||||
} from '@atproto/api'
|
||||
import {useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {isNetworkError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {useCreateGroupChat} from '#/state/queries/messages/create-group-chat'
|
||||
import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {SearchablePeopleList} from '#/components/dialogs/SearchablePeopleList'
|
||||
import {InitiateChatFlow} from '#/components/dms/InitiateChatFlow'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
|
||||
@@ -16,26 +22,39 @@ export function SendViaChatDialog({
|
||||
control: Dialog.DialogControlProps
|
||||
onSelectChat: (chatId: string) => void
|
||||
}) {
|
||||
const [flowKey, setFlowKey] = useState(0)
|
||||
const onClose = useCallback(() => setFlowKey(key => key + 1), [])
|
||||
|
||||
return (
|
||||
<Dialog.Outer
|
||||
control={control}
|
||||
testID="sendViaChatChatDialog"
|
||||
nativeOptions={{fullHeight: true}}>
|
||||
nativeOptions={{fullHeight: true}}
|
||||
onClose={onClose}>
|
||||
<Dialog.Handle />
|
||||
<SendViaChatDialogInner control={control} onSelectChat={onSelectChat} />
|
||||
<SendViaChatDialogInner
|
||||
control={control}
|
||||
flowKey={flowKey}
|
||||
onSelectChat={onSelectChat}
|
||||
/>
|
||||
</Dialog.Outer>
|
||||
)
|
||||
}
|
||||
|
||||
function SendViaChatDialogInner({
|
||||
control,
|
||||
flowKey,
|
||||
onSelectChat,
|
||||
}: {
|
||||
control: Dialog.DialogControlProps
|
||||
flowKey: number
|
||||
onSelectChat: (chatId: string) => void
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const {t: l} = useLingui()
|
||||
const ax = useAnalytics()
|
||||
|
||||
const isGroupChatEnabled = !ax.features.enabled(ax.features.GroupChatsDisable)
|
||||
|
||||
const {mutate: createChat} = useGetConvoForMembers({
|
||||
onSuccess: data => {
|
||||
onSelectChat(data.convo.id)
|
||||
@@ -46,8 +65,74 @@ function SendViaChatDialogInner({
|
||||
ax.metric('chat:open', {logContext: 'SendViaChatDialog'})
|
||||
},
|
||||
onError: error => {
|
||||
logger.error('Failed to share post to chat', {message: error})
|
||||
Toast.show(_(msg`An issue occurred while trying to open the chat`), {
|
||||
logger.error('Failed to share post to chat', {safeMessage: error})
|
||||
let errorMessage = l`An issue occurred starting the chat, please try again.`
|
||||
if (isNetworkError(error)) {
|
||||
errorMessage = l`A network error occurred. Please check your internet connection.`
|
||||
} else if (
|
||||
error instanceof ChatBskyConvoGetConvoForMembers.AccountSuspendedError
|
||||
) {
|
||||
errorMessage = l`Suspended accounts cannot participate in chat.`
|
||||
} else if (
|
||||
error instanceof ChatBskyConvoGetConvoForMembers.BlockedActorError
|
||||
) {
|
||||
errorMessage = l`This user has blocked you and cannot be messaged.`
|
||||
} else if (
|
||||
error instanceof ChatBskyConvoGetConvoForMembers.MessagesDisabledError
|
||||
) {
|
||||
errorMessage = l`This user has disabled chat and cannot be messaged.`
|
||||
} else if (
|
||||
error instanceof
|
||||
ChatBskyConvoGetConvoForMembers.NotFollowedBySenderError
|
||||
) {
|
||||
errorMessage = l`Chat recipient is not followed by the sender.`
|
||||
} else if (
|
||||
error instanceof ChatBskyConvoGetConvoForMembers.RecipientNotFoundError
|
||||
) {
|
||||
errorMessage = l`Unable to find the selected recipient.`
|
||||
}
|
||||
Toast.show(errorMessage, {
|
||||
type: 'error',
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const {mutate: createGroupChat} = useCreateGroupChat({
|
||||
onSuccess: data => {
|
||||
onSelectChat(data.convo.id)
|
||||
|
||||
ax.metric('groupchat:create', {logContext: 'SendViaChatDialog'})
|
||||
},
|
||||
onError: error => {
|
||||
logger.error('Failed to share post to group chat', {safeMessage: error})
|
||||
let errorMessage = l`An issue occurred starting the group chat, please try again.`
|
||||
if (isNetworkError(error)) {
|
||||
errorMessage = l`A network error occurred. Please check your internet connection.`
|
||||
} else if (
|
||||
error instanceof ChatBskyGroupCreateGroup.AccountSuspendedError
|
||||
) {
|
||||
errorMessage = l`Suspended accounts cannot participate in a group chat.`
|
||||
} else if (error instanceof ChatBskyGroupCreateGroup.BlockedActorError) {
|
||||
errorMessage = l`One of the selected recipients has blocked you and cannot be messaged.`
|
||||
} else if (
|
||||
error instanceof
|
||||
ChatBskyGroupCreateGroup.NewAccountCannotCreateGroupError
|
||||
) {
|
||||
errorMessage = l`You cannot create a group chat yet.`
|
||||
} else if (
|
||||
error instanceof ChatBskyGroupCreateGroup.NotFollowedBySenderError
|
||||
) {
|
||||
errorMessage = l`A selected recipient is not followed by the sender.`
|
||||
} else if (
|
||||
error instanceof ChatBskyGroupCreateGroup.RecipientNotFoundError
|
||||
) {
|
||||
errorMessage = l`Unable to find a selected recipient.`
|
||||
} else if (
|
||||
error instanceof ChatBskyGroupCreateGroup.UserForbidsGroupsError
|
||||
) {
|
||||
errorMessage = l`One of the selected recipients does not allow group chats.`
|
||||
}
|
||||
Toast.show(errorMessage, {
|
||||
type: 'error',
|
||||
})
|
||||
},
|
||||
@@ -67,9 +152,28 @@ function SendViaChatDialogInner({
|
||||
[control, createChat],
|
||||
)
|
||||
|
||||
return (
|
||||
const onCreateGroupChat = useCallback(
|
||||
(members: string[], name: string) => {
|
||||
control.close(() => {
|
||||
createGroupChat({members, name})
|
||||
})
|
||||
},
|
||||
[control, createGroupChat],
|
||||
)
|
||||
|
||||
return isGroupChatEnabled ? (
|
||||
<InitiateChatFlow
|
||||
key={flowKey}
|
||||
title={l`Send post to...`}
|
||||
onSelectChat={onCreateChat}
|
||||
onSelectExistingChat={onSelectExistingChat}
|
||||
onSelectGroupChat={onCreateGroupChat}
|
||||
showRecentConvos
|
||||
sortByMessageDeclaration
|
||||
/>
|
||||
) : (
|
||||
<SearchablePeopleList
|
||||
title={_(msg`Send post to...`)}
|
||||
title={l`Send post to...`}
|
||||
onSelectChat={chat => {
|
||||
if (chat.kind === 'user') {
|
||||
onCreateChat(chat.did)
|
||||
|
||||
Reference in New Issue
Block a user