Update chat requests header (#10446)
This commit is contained in:
@@ -139,11 +139,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/dialogs/GifSelect.tsx": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/dialogs/LanguageSelectDialog.tsx": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 1
|
||||
@@ -579,21 +574,6 @@
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"src/view/com/lightbox/ImageViewing/@types/index.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/view/com/lightbox/ImageViewing/index.tsx": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/view/com/lists/ListMembers.tsx": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 3
|
||||
@@ -709,11 +689,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/view/com/util/fab/FABInner.tsx": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/view/screens/Debug.tsx": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 1
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {useCallback, useEffect, useMemo, useState} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {useAnimatedRef} from 'react-native-reanimated'
|
||||
import {type ChatBskyActorDefs, type ChatBskyConvoDefs} from '@atproto/api'
|
||||
import {type ChatBskyConvoDefs} from '@atproto/api'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans} from '@lingui/react/macro'
|
||||
@@ -19,7 +19,6 @@ import {MESSAGE_SCREEN_POLL_INTERVAL} from '#/state/messages/convo/const'
|
||||
import {useMessagesEventBus} from '#/state/messages/events'
|
||||
import {useLeftConvos} from '#/state/queries/messages/leave-conversation'
|
||||
import {useListConvosQuery} from '#/state/queries/messages/list-conversations'
|
||||
import {useSession} from '#/state/session'
|
||||
import {List, type ListRef} from '#/view/com/util/List'
|
||||
import {ChatListLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||
@@ -41,30 +40,19 @@ import {Text} from '#/components/Typography'
|
||||
import {useAgeAssurance} from '#/ageAssurance'
|
||||
import {IS_NATIVE} from '#/env'
|
||||
import {ChatListItem} from './components/ChatListItem'
|
||||
import {InboxPreview} from './components/InboxPreview'
|
||||
import {InboxRequests} from './components/InboxRequests'
|
||||
|
||||
type ListItem =
|
||||
| {
|
||||
type: 'INBOX'
|
||||
count: number
|
||||
profiles: ChatBskyActorDefs.ProfileViewBasic[]
|
||||
}
|
||||
| {
|
||||
type: 'CONVERSATION'
|
||||
conversation: ChatBskyConvoDefs.ConvoView
|
||||
}
|
||||
type ListItem = {
|
||||
type: 'CONVERSATION'
|
||||
conversation: ChatBskyConvoDefs.ConvoView
|
||||
}
|
||||
|
||||
function renderItem({item}: {item: ListItem}) {
|
||||
switch (item.type) {
|
||||
case 'INBOX':
|
||||
return <InboxPreview profiles={item.profiles} />
|
||||
case 'CONVERSATION':
|
||||
return <ChatListItem convo={item.conversation} />
|
||||
}
|
||||
return <ChatListItem convo={item.conversation} />
|
||||
}
|
||||
|
||||
function keyExtractor(item: ListItem) {
|
||||
return item.type === 'INBOX' ? 'INBOX' : item.conversation.id
|
||||
return item.conversation.id
|
||||
}
|
||||
|
||||
type Props = NativeStackScreenProps<MessagesTabNavigatorParams, 'Messages'>
|
||||
@@ -99,7 +87,6 @@ export function MessagesScreen(props: Props) {
|
||||
export function MessagesScreenInner({navigation, route}: Props) {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const {currentAccount} = useSession()
|
||||
const newChatControl = useDialogControl()
|
||||
const scrollElRef: ListRef = useAnimatedRef()
|
||||
const pushToConversation = route.params?.pushToConversation
|
||||
@@ -147,7 +134,11 @@ export function MessagesScreenInner({navigation, route}: Props) {
|
||||
refetch,
|
||||
} = useListConvosQuery({status: 'accepted'})
|
||||
|
||||
const {data: inboxData, refetch: refetchInbox} = useListConvosQuery({
|
||||
const {
|
||||
data: inboxData,
|
||||
refetch: refetchInbox,
|
||||
hasNextPage: hasMoreRequests,
|
||||
} = useListConvosQuery({
|
||||
status: 'request',
|
||||
})
|
||||
|
||||
@@ -165,15 +156,6 @@ export function MessagesScreenInner({navigation, route}: Props) {
|
||||
!convo.muted &&
|
||||
convo.members.every(member => member.handle !== 'missing.invalid'),
|
||||
) ?? []
|
||||
const hasInboxConvos = inboxAllConvos?.length > 0
|
||||
|
||||
const inboxUnreadConvos = inboxAllConvos.filter(
|
||||
convo => convo.unreadCount > 0,
|
||||
)
|
||||
|
||||
const inboxUnreadConvoMembers = inboxUnreadConvos
|
||||
.map(x => x.members.find(y => y.did !== currentAccount?.did))
|
||||
.filter(x => !!x)
|
||||
|
||||
const conversations = useMemo(() => {
|
||||
if (data?.pages) {
|
||||
@@ -183,15 +165,6 @@ export function MessagesScreenInner({navigation, route}: Props) {
|
||||
.filter(convo => !leftConvos.includes(convo.id))
|
||||
|
||||
return [
|
||||
...(hasInboxConvos
|
||||
? [
|
||||
{
|
||||
type: 'INBOX' as const,
|
||||
count: inboxUnreadConvoMembers.length,
|
||||
profiles: inboxUnreadConvoMembers.slice(0, 3),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...conversations.map(
|
||||
convo =>
|
||||
({
|
||||
@@ -202,7 +175,7 @@ export function MessagesScreenInner({navigation, route}: Props) {
|
||||
] satisfies ListItem[]
|
||||
}
|
||||
return []
|
||||
}, [data, leftConvos, hasInboxConvos, inboxUnreadConvoMembers])
|
||||
}, [data, leftConvos])
|
||||
|
||||
const onRefresh = useCallback(async () => {
|
||||
setIsPTRing(true)
|
||||
@@ -258,11 +231,12 @@ export function MessagesScreenInner({navigation, route}: Props) {
|
||||
if (activeConversations.length === 0) {
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<Header newChatControl={newChatControl} />
|
||||
<Header
|
||||
newChatControl={newChatControl}
|
||||
requestsCount={inboxAllConvos.length}
|
||||
hasMoreRequests={hasMoreRequests}
|
||||
/>
|
||||
<Layout.Center>
|
||||
{!isLoading && hasInboxConvos && (
|
||||
<InboxPreview profiles={inboxUnreadConvoMembers} />
|
||||
)}
|
||||
{isLoading ? (
|
||||
<ChatListLoadingPlaceholder />
|
||||
) : (
|
||||
@@ -338,7 +312,11 @@ export function MessagesScreenInner({navigation, route}: Props) {
|
||||
|
||||
return (
|
||||
<Layout.Screen testID="messagesScreen">
|
||||
<Header newChatControl={newChatControl} />
|
||||
<Header
|
||||
newChatControl={newChatControl}
|
||||
requestsCount={inboxAllConvos.length}
|
||||
hasMoreRequests={hasMoreRequests}
|
||||
/>
|
||||
<NewChat onNewChat={onNewChat} control={newChatControl} />
|
||||
<List
|
||||
ref={scrollElRef}
|
||||
@@ -367,7 +345,15 @@ export function MessagesScreenInner({navigation, route}: Props) {
|
||||
)
|
||||
}
|
||||
|
||||
function Header({newChatControl}: {newChatControl: DialogControlProps}) {
|
||||
function Header({
|
||||
newChatControl,
|
||||
requestsCount,
|
||||
hasMoreRequests,
|
||||
}: {
|
||||
newChatControl: DialogControlProps
|
||||
requestsCount: number
|
||||
hasMoreRequests: boolean
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const requireEmailVerification = useRequireEmailVerification()
|
||||
@@ -383,6 +369,10 @@ function Header({newChatControl}: {newChatControl: DialogControlProps}) {
|
||||
],
|
||||
})
|
||||
|
||||
const requestsLink = (
|
||||
<InboxRequests count={requestsCount} more={hasMoreRequests} />
|
||||
)
|
||||
|
||||
const settingsLink = (
|
||||
<Link
|
||||
to="/messages/settings"
|
||||
@@ -407,6 +397,7 @@ function Header({newChatControl}: {newChatControl: DialogControlProps}) {
|
||||
</Layout.Header.Content>
|
||||
|
||||
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
|
||||
{requestsLink}
|
||||
{settingsLink}
|
||||
<Button
|
||||
label={_(msg`New chat`)}
|
||||
@@ -424,11 +415,12 @@ function Header({newChatControl}: {newChatControl: DialogControlProps}) {
|
||||
) : (
|
||||
<>
|
||||
<Layout.Header.MenuButton />
|
||||
<Layout.Header.Content>
|
||||
<Layout.Header.Content align="left">
|
||||
<Layout.Header.TitleText>
|
||||
<Trans>Chats</Trans>
|
||||
</Layout.Header.TitleText>
|
||||
</Layout.Header.Content>
|
||||
{requestsLink}
|
||||
<Layout.Header.Slot>{settingsLink}</Layout.Header.Slot>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -4,9 +4,7 @@ import {
|
||||
type ChatBskyConvoDefs,
|
||||
type ChatBskyConvoListConvos,
|
||||
} 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 {useFocusEffect, useNavigation} from '@react-navigation/native'
|
||||
import {
|
||||
type InfiniteData,
|
||||
@@ -50,11 +48,11 @@ import {RequestListItem} from './components/RequestListItem'
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'MessagesInbox'>
|
||||
|
||||
export function MessagesInboxScreen(props: Props) {
|
||||
const {_} = useLingui()
|
||||
const {t: l} = useLingui()
|
||||
const aaCopy = useAgeAssuranceCopy()
|
||||
return (
|
||||
<AgeRestrictedScreen
|
||||
screenTitle={_(msg`Chat requests`)}
|
||||
screenTitle={l`Chat requests`}
|
||||
infoText={aaCopy.chatsInfoText}>
|
||||
<MessagesInboxScreenInner {...props} />
|
||||
</AgeRestrictedScreen>
|
||||
@@ -126,7 +124,7 @@ function RequestList({
|
||||
conversations: ChatBskyConvoDefs.ConvoView[]
|
||||
hasUnreadConvos: boolean
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const {t: l} = useLingui()
|
||||
const t = useTheme()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
|
||||
@@ -207,15 +205,15 @@ function RequestList({
|
||||
t.atoms.text_contrast_medium,
|
||||
{maxWidth: 360},
|
||||
]}>
|
||||
{cleanError(error) || _(msg`Failed to load conversations`)}
|
||||
{cleanError(error) || l`Failed to load conversations`}
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
label={_(msg`Reload conversations`)}
|
||||
label={l`Reload conversations`}
|
||||
size="small"
|
||||
color="secondary_inverted"
|
||||
variant="solid"
|
||||
onPress={() => refetch()}>
|
||||
onPress={() => void refetch()}>
|
||||
<ButtonText>
|
||||
<Trans>Retry</Trans>
|
||||
</ButtonText>
|
||||
@@ -249,7 +247,7 @@ function RequestList({
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
size="small"
|
||||
label={_(msg`Go back`)}
|
||||
label={l`Go back`}
|
||||
onPress={() => {
|
||||
if (navigation.canGoBack()) {
|
||||
navigation.goBack()
|
||||
@@ -278,8 +276,8 @@ function RequestList({
|
||||
renderItem={renderItem}
|
||||
keyExtractor={keyExtractor}
|
||||
refreshing={isPTRing}
|
||||
onRefresh={onRefresh}
|
||||
onEndReached={onEndReached}
|
||||
onRefresh={() => void onRefresh()}
|
||||
onEndReached={() => void onEndReached()}
|
||||
ListFooterComponent={
|
||||
<ListFooter
|
||||
isFetchingNextPage={isFetchingNextPage}
|
||||
@@ -309,16 +307,16 @@ function renderItem({item}: {item: ChatBskyConvoDefs.ConvoView}) {
|
||||
}
|
||||
|
||||
function MarkAllReadFAB() {
|
||||
const {_} = useLingui()
|
||||
const {t: l} = useLingui()
|
||||
const t = useTheme()
|
||||
const {mutate: markAllRead} = useUpdateAllRead('request', {
|
||||
onMutate: () => {
|
||||
Toast.show(_(msg`Marked all as read`), {
|
||||
Toast.show(l`Marked all as read`, {
|
||||
type: 'success',
|
||||
})
|
||||
},
|
||||
onError: () => {
|
||||
Toast.show(_(msg`Failed to mark all requests as read`), {
|
||||
Toast.show(l`Failed to mark all requests as read`, {
|
||||
type: 'error',
|
||||
})
|
||||
},
|
||||
@@ -330,22 +328,22 @@ function MarkAllReadFAB() {
|
||||
onPress={() => markAllRead()}
|
||||
icon={<CheckIcon size="lg" fill={t.palette.white} />}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Mark all as read`)}
|
||||
accessibilityLabel={l`Mark all as read`}
|
||||
accessibilityHint=""
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function MarkAsReadHeaderButton() {
|
||||
const {_} = useLingui()
|
||||
const {t: l} = useLingui()
|
||||
const {mutate: markAllRead} = useUpdateAllRead('request', {
|
||||
onMutate: () => {
|
||||
Toast.show(_(msg`Marked all as read`), {
|
||||
Toast.show(l`Marked all as read`, {
|
||||
type: 'success',
|
||||
})
|
||||
},
|
||||
onError: () => {
|
||||
Toast.show(_(msg`Failed to mark all requests as read`), {
|
||||
Toast.show(l`Failed to mark all requests as read`, {
|
||||
type: 'error',
|
||||
})
|
||||
},
|
||||
@@ -353,7 +351,7 @@ function MarkAsReadHeaderButton() {
|
||||
|
||||
return (
|
||||
<Button
|
||||
label={_(msg`Mark all as read`)}
|
||||
label={l`Mark all as read`}
|
||||
size="small"
|
||||
color="secondary"
|
||||
variant="solid"
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import {View} from 'react-native'
|
||||
import {type ChatBskyActorDefs} from '@atproto/api'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Trans} from '@lingui/react/macro'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {AvatarStack} from '#/components/AvatarStack'
|
||||
import {ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {ArrowRight_Stroke2_Corner0_Rounded as ArrowRightIcon} from '#/components/icons/Arrow'
|
||||
import {Envelope_Stroke2_Corner2_Rounded as EnvelopeIcon} from '#/components/icons/Envelope'
|
||||
import {Link} from '#/components/Link'
|
||||
|
||||
export function InboxPreview({
|
||||
profiles,
|
||||
}: {
|
||||
profiles: ChatBskyActorDefs.ProfileViewBasic[]
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
return (
|
||||
<Link
|
||||
label={_(msg`Chat request inbox`)}
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.px_xl,
|
||||
a.py_sm,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.gap_md,
|
||||
a.border_t,
|
||||
{marginTop: a.border_t.borderTopWidth * -1},
|
||||
a.border_b,
|
||||
t.atoms.border_contrast_low,
|
||||
{minHeight: 44},
|
||||
a.rounded_0,
|
||||
]}
|
||||
to="/messages/inbox"
|
||||
color="secondary"
|
||||
variant="solid">
|
||||
<View style={[a.relative]}>
|
||||
<ButtonIcon icon={EnvelopeIcon} size="lg" />
|
||||
{profiles.length > 0 && (
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
a.rounded_full,
|
||||
a.z_20,
|
||||
{
|
||||
top: -4,
|
||||
right: -5,
|
||||
width: 10,
|
||||
height: 10,
|
||||
backgroundColor: t.palette.primary_500,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<ButtonText
|
||||
style={[a.flex_1, a.font_semi_bold, a.text_left]}
|
||||
numberOfLines={1}>
|
||||
<Trans>Chat requests</Trans>
|
||||
</ButtonText>
|
||||
<AvatarStack
|
||||
profiles={profiles}
|
||||
backgroundColor={t.atoms.bg_contrast_25.backgroundColor}
|
||||
/>
|
||||
<ButtonIcon icon={ArrowRightIcon} size="lg" />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import {plural} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {UNREAD_LIMIT} from '#/state/queries/messages/list-conversations'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {InlineLinkText} from '#/components/Link'
|
||||
|
||||
export function InboxRequests({count, more}: {count: number; more: boolean}) {
|
||||
const {t: l} = useLingui()
|
||||
|
||||
if (count < 1) return null
|
||||
|
||||
const label =
|
||||
count >= UNREAD_LIMIT && more
|
||||
? l({
|
||||
message: `${count}+ requests`,
|
||||
comment: 'Displayed when the number of requests is greater than 20',
|
||||
})
|
||||
: plural(count, {
|
||||
one: '# request',
|
||||
other: '# requests',
|
||||
})
|
||||
|
||||
return (
|
||||
<InlineLinkText
|
||||
label={label}
|
||||
to="/messages/inbox"
|
||||
style={[a.text_md, a.font_medium]}>
|
||||
{label}
|
||||
</InlineLinkText>
|
||||
)
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import {parseConvoView} from '#/components/dms/util'
|
||||
import {useLeftConvos} from './leave-conversation'
|
||||
|
||||
const DEFAULT_LIMIT = 10
|
||||
export const UNREAD_LIMIT = 20
|
||||
|
||||
export const RQKEY_ROOT = 'convo-list'
|
||||
export const RQKEY = (
|
||||
@@ -102,7 +103,10 @@ export function ListConvosProviderInner({
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const {refetch, data} = useListConvosQuery({readState: 'unread', limit: 20})
|
||||
const {refetch, data} = useListConvosQuery({
|
||||
readState: 'unread',
|
||||
limit: UNREAD_LIMIT,
|
||||
})
|
||||
const messagesBus = useMessagesEventBus()
|
||||
const queryClient = useQueryClient()
|
||||
const {currentConvoId} = useCurrentConvoId()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {type ComponentProps, type JSX} from 'react'
|
||||
import {
|
||||
type GestureResponderEvent,
|
||||
type Pressable,
|
||||
type StyleProp,
|
||||
StyleSheet,
|
||||
@@ -49,7 +50,7 @@ export function FABInner({testID, icon, onPress, style, ...props}: FABProps) {
|
||||
onPress?.(evt)
|
||||
playHaptic('Light')
|
||||
}}
|
||||
onLongPress={ios((evt: any) => {
|
||||
onLongPress={ios((evt: GestureResponderEvent) => {
|
||||
onPress?.(evt)
|
||||
playHaptic('Heavy')
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user