[🐴] Show if user can be messaged in new chat search (#4021)
* show if user can be messaged * allow 2 lines in handle field due to new text * cannot -> can't * rework canBeMessaged logic and move to new file --------- Co-authored-by: Eric Bailey <git@esb.lol>zio/stable
parent
2121b5f86f
commit
ed8922281a
|
@ -136,6 +136,7 @@ let ListMaybePlaceholder = ({
|
|||
onGoBack,
|
||||
hideBackButton,
|
||||
sideBorders,
|
||||
topBorder = true,
|
||||
}: {
|
||||
isLoading: boolean
|
||||
noEmpty?: boolean
|
||||
|
@ -149,6 +150,7 @@ let ListMaybePlaceholder = ({
|
|||
onGoBack?: () => void
|
||||
hideBackButton?: boolean
|
||||
sideBorders?: boolean
|
||||
topBorder?: boolean
|
||||
}): React.ReactNode => {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
|
@ -165,7 +167,7 @@ let ListMaybePlaceholder = ({
|
|||
{paddingTop: 175, paddingBottom: 110},
|
||||
]}
|
||||
sideBorders={sideBorders ?? gtMobile}
|
||||
topBorder={!gtTablet}>
|
||||
topBorder={topBorder && !gtTablet}>
|
||||
<View style={[a.w_full, a.align_center, {top: 100}]}>
|
||||
<Loader size="xl" />
|
||||
</View>
|
||||
|
|
|
@ -197,6 +197,7 @@ function GifList({
|
|||
onGoBack={onGoBack}
|
||||
emptyType="results"
|
||||
sideBorders={false}
|
||||
topBorder={false}
|
||||
errorTitle={_(msg`Failed to load GIFs`)}
|
||||
errorMessage={_(msg`There was an issue connecting to Tenor.`)}
|
||||
emptyMessage={
|
||||
|
|
|
@ -10,6 +10,7 @@ import {sanitizeHandle} from '#/lib/strings/handles'
|
|||
import {isWeb} from '#/platform/detection'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useActorAutocompleteQuery} from 'state/queries/actor-autocomplete'
|
||||
import {FAB} from '#/view/com/util/fab/FAB'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
|
@ -23,6 +24,7 @@ import {Button} from '../Button'
|
|||
import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '../icons/Envelope'
|
||||
import {ListMaybePlaceholder} from '../Lists'
|
||||
import {Text} from '../Typography'
|
||||
import {canBeMessaged} from './util'
|
||||
|
||||
export function NewChat({
|
||||
control,
|
||||
|
@ -82,6 +84,7 @@ function SearchablePeopleList({
|
|||
const moderationOpts = useModerationOpts()
|
||||
const control = Dialog.useDialogContext()
|
||||
const listRef = useRef<BottomSheetFlatListMethods>(null)
|
||||
const {currentAccount} = useSession()
|
||||
|
||||
const [searchText, setSearchText] = useState('')
|
||||
|
||||
|
@ -95,12 +98,17 @@ function SearchablePeopleList({
|
|||
const renderItem = useCallback(
|
||||
({item: profile}: {item: AppBskyActorDefs.ProfileView}) => {
|
||||
if (!moderationOpts) return null
|
||||
|
||||
const moderation = moderateProfile(profile, moderationOpts)
|
||||
|
||||
const disabled = !canBeMessaged(profile)
|
||||
const handle = sanitizeHandle(profile.handle, '@')
|
||||
|
||||
return (
|
||||
<Button
|
||||
label={profile.displayName || sanitizeHandle(profile.handle)}
|
||||
onPress={() => onCreateChat(profile.did)}>
|
||||
{({hovered, pressed}) => (
|
||||
onPress={() => !disabled && onCreateChat(profile.did)}>
|
||||
{({hovered, pressed, focused}) => (
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
|
@ -110,7 +118,9 @@ function SearchablePeopleList({
|
|||
a.align_center,
|
||||
a.flex_row,
|
||||
a.rounded_sm,
|
||||
pressed
|
||||
disabled
|
||||
? {opacity: 0.5}
|
||||
: pressed || focused
|
||||
? t.atoms.bg_contrast_25
|
||||
: hovered
|
||||
? t.atoms.bg_contrast_50
|
||||
|
@ -131,8 +141,12 @@ function SearchablePeopleList({
|
|||
moderation.ui('displayName'),
|
||||
)}
|
||||
</Text>
|
||||
<Text style={t.atoms.text_contrast_high} numberOfLines={1}>
|
||||
{sanitizeHandle(profile.handle, '@')}
|
||||
<Text style={t.atoms.text_contrast_high} numberOfLines={2}>
|
||||
{disabled ? (
|
||||
<Trans>{handle} can't be messaged</Trans>
|
||||
) : (
|
||||
handle
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
@ -166,7 +180,6 @@ function SearchablePeopleList({
|
|||
t.atoms.bg,
|
||||
]}
|
||||
/>
|
||||
<Dialog.Close />
|
||||
<Text
|
||||
style={[
|
||||
a.text_2xl,
|
||||
|
@ -201,14 +214,23 @@ function SearchablePeopleList({
|
|||
autoFocus
|
||||
/>
|
||||
</TextField.Root>
|
||||
<Dialog.Close />
|
||||
</View>
|
||||
)
|
||||
}, [t.atoms.bg, _, control, searchText])
|
||||
|
||||
const dataWithoutSelf = useMemo(() => {
|
||||
return (
|
||||
actorAutocompleteData?.filter(
|
||||
profile => profile.did !== currentAccount?.did,
|
||||
) ?? []
|
||||
)
|
||||
}, [actorAutocompleteData, currentAccount?.did])
|
||||
|
||||
return (
|
||||
<Dialog.InnerFlatList
|
||||
ref={listRef}
|
||||
data={actorAutocompleteData}
|
||||
data={dataWithoutSelf}
|
||||
renderItem={renderItem}
|
||||
ListHeaderComponent={
|
||||
<>
|
||||
|
@ -235,6 +257,7 @@ function SearchablePeopleList({
|
|||
hideBackButton={true}
|
||||
emptyType="results"
|
||||
sideBorders={false}
|
||||
topBorder={false}
|
||||
emptyMessage={
|
||||
isError
|
||||
? _(msg`No search results found for "${searchText}".`)
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import {AppBskyActorDefs} from '@atproto/api'
|
||||
|
||||
export function canBeMessaged(profile: AppBskyActorDefs.ProfileView) {
|
||||
switch (profile.associated?.chat?.allowIncoming) {
|
||||
case 'none':
|
||||
return false
|
||||
case 'all':
|
||||
return true
|
||||
// if unset, treat as following
|
||||
case 'following':
|
||||
case undefined:
|
||||
return Boolean(profile.viewer?.followedBy)
|
||||
// any other values are invalid according to the lexicon, so
|
||||
// let's treat as false to be safe
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react'
|
||||
import React, {useCallback, useState} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {
|
||||
AppBskyActorDefs,
|
||||
|
@ -88,22 +88,22 @@ function ChatListItemReady({
|
|||
}
|
||||
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const [showActions, setShowActions] = React.useState(false)
|
||||
const [showActions, setShowActions] = useState(false)
|
||||
|
||||
const onMouseEnter = React.useCallback(() => {
|
||||
const onMouseEnter = useCallback(() => {
|
||||
setShowActions(true)
|
||||
}, [])
|
||||
|
||||
const onMouseLeave = React.useCallback(() => {
|
||||
const onMouseLeave = useCallback(() => {
|
||||
setShowActions(false)
|
||||
}, [])
|
||||
|
||||
const onFocus = React.useCallback<React.FocusEventHandler>(e => {
|
||||
const onFocus = useCallback<React.FocusEventHandler>(e => {
|
||||
if (e.nativeEvent.relatedTarget == null) return
|
||||
setShowActions(true)
|
||||
}, [])
|
||||
|
||||
const onPress = React.useCallback(() => {
|
||||
const onPress = useCallback(() => {
|
||||
navigation.push('MessagesConversation', {
|
||||
conversation: convo.id,
|
||||
})
|
||||
|
@ -119,9 +119,9 @@ function ChatListItemReady({
|
|||
<Button
|
||||
label={profile.displayName || profile.handle}
|
||||
onPress={onPress}
|
||||
style={a.flex_1}
|
||||
style={[a.flex_1]}
|
||||
onLongPress={isNative ? menuControl.open : undefined}>
|
||||
{({hovered, pressed}) => (
|
||||
{({hovered, pressed, focused}) => (
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
|
@ -129,7 +129,7 @@ function ChatListItemReady({
|
|||
a.px_lg,
|
||||
a.py_md,
|
||||
a.gap_md,
|
||||
(hovered || pressed) && t.atoms.bg_contrast_25,
|
||||
(hovered || pressed || focused) && t.atoms.bg_contrast_25,
|
||||
t.atoms.border_contrast_low,
|
||||
]}>
|
||||
<UserAvatar
|
||||
|
|
Loading…
Reference in New Issue