[Clipclops] Use API data for clipclop list (#3769)
* use real API * remove extra tab icon * messages list web layout + style improvements * use style's text color for input * make new chat button way more obvious --------- Co-authored-by: Hailey <me@haileyok.com>zio/stable
parent
bcd3678067
commit
7b694fd860
|
@ -23,8 +23,13 @@ import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '../icons/Envelope'
|
|||
import {ListMaybePlaceholder} from '../Lists'
|
||||
import {Text} from '../Typography'
|
||||
|
||||
export function NewChat({onNewChat}: {onNewChat: (chatId: string) => void}) {
|
||||
const control = Dialog.useDialogControl()
|
||||
export function NewChat({
|
||||
control,
|
||||
onNewChat,
|
||||
}: {
|
||||
control: Dialog.DialogControlProps
|
||||
onNewChat: (chatId: string) => void
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ export function MessageInput({
|
|||
value={message}
|
||||
onChangeText={setMessage}
|
||||
placeholder="Write a message"
|
||||
style={[a.flex_1, a.text_sm, a.px_sm]}
|
||||
style={[a.flex_1, a.text_sm, a.px_sm, t.atoms.text]}
|
||||
onSubmitEditing={onSubmit}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
/* eslint-disable react/prop-types */
|
||||
|
||||
import React, {useCallback, useMemo, useState} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||
import {useInfiniteQuery} from '@tanstack/react-query'
|
||||
|
||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||
import {MessagesTabNavigatorParams} from '#/lib/routes/types'
|
||||
|
@ -14,19 +15,26 @@ import {useAgent} from '#/state/session'
|
|||
import {List} from '#/view/com/util/List'
|
||||
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {useTheme} from '#/alf'
|
||||
import {useBreakpoints, useTheme} from '#/alf'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {DialogControlProps, useDialogControl} from '#/components/Dialog'
|
||||
import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '#/components/icons/Envelope'
|
||||
import {SettingsSliderVertical_Stroke2_Corner0_Rounded as SettingsSlider} from '#/components/icons/SettingsSlider'
|
||||
import {Link} from '#/components/Link'
|
||||
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
||||
import {Text} from '#/components/Typography'
|
||||
import * as TempDmChatDefs from '#/temp/dm/defs'
|
||||
import {NewChat} from '../../../components/dms/NewChat'
|
||||
import {ClipClopGate} from '../gate'
|
||||
import {useListChats} from '../Temp/query/query'
|
||||
|
||||
type Props = NativeStackScreenProps<MessagesTabNavigatorParams, 'MessagesList'>
|
||||
export function MessagesListScreen({navigation}: Props) {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const newChatControl = useDialogControl()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
|
||||
const renderButton = useCallback(() => {
|
||||
return (
|
||||
|
@ -50,13 +58,13 @@ export function MessagesListScreen({navigation}: Props) {
|
|||
fetchNextPage,
|
||||
error,
|
||||
refetch,
|
||||
} = usePlaceholderConversations()
|
||||
} = useListChats()
|
||||
|
||||
const isError = !!error
|
||||
|
||||
const conversations = useMemo(() => {
|
||||
if (data?.pages) {
|
||||
return data.pages.flat()
|
||||
return data.pages.flatMap(page => page.chats)
|
||||
}
|
||||
return []
|
||||
}, [data])
|
||||
|
@ -86,6 +94,14 @@ export function MessagesListScreen({navigation}: Props) {
|
|||
[navigation],
|
||||
)
|
||||
|
||||
const onNavigateToSettings = useCallback(() => {
|
||||
navigation.navigate('MessagesSettings')
|
||||
}, [navigation])
|
||||
|
||||
const renderItem = useCallback(({item}: {item: TempDmChatDefs.ChatView}) => {
|
||||
return <ChatListItem key={item.id} chat={item} />
|
||||
}, [])
|
||||
|
||||
const gate = useGate()
|
||||
if (!gate('dms')) return <ClipClopGate />
|
||||
|
||||
|
@ -102,73 +118,35 @@ export function MessagesListScreen({navigation}: Props) {
|
|||
errorMessage={cleanError(error)}
|
||||
onRetry={isError ? refetch : undefined}
|
||||
/>
|
||||
<NewChat onNewChat={onNewChat} />
|
||||
<NewChat onNewChat={onNewChat} control={newChatControl} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={a.flex_1}>
|
||||
<ViewHeader
|
||||
title={_(msg`Messages`)}
|
||||
showOnDesktop
|
||||
renderButton={renderButton}
|
||||
showBorder
|
||||
canGoBack={false}
|
||||
/>
|
||||
<NewChat onNewChat={onNewChat} />
|
||||
{!gtMobile && (
|
||||
<ViewHeader
|
||||
title={_(msg`Messages`)}
|
||||
renderButton={renderButton}
|
||||
showBorder
|
||||
canGoBack={false}
|
||||
/>
|
||||
)}
|
||||
<NewChat onNewChat={onNewChat} control={newChatControl} />
|
||||
<List
|
||||
data={conversations}
|
||||
renderItem={({item}) => {
|
||||
return (
|
||||
<Link
|
||||
to={`/messages/3kqzb4mytxk2v`}
|
||||
style={[a.flex_1, a.pl_md, a.py_sm, a.gap_md, a.pr_2xl]}>
|
||||
<PreviewableUserAvatar profile={item.profile} size={44} />
|
||||
<View style={[a.flex_1]}>
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_between,
|
||||
a.gap_lg,
|
||||
a.flex_1,
|
||||
]}>
|
||||
<Text numberOfLines={1}>
|
||||
<Text style={item.unread && a.font_bold}>
|
||||
{item.profile.displayName || item.profile.handle}
|
||||
</Text>{' '}
|
||||
<Text style={t.atoms.text_contrast_medium}>
|
||||
@{item.profile.handle}
|
||||
</Text>
|
||||
</Text>
|
||||
{item.unread && (
|
||||
<View
|
||||
style={[
|
||||
a.ml_2xl,
|
||||
{backgroundColor: t.palette.primary_500},
|
||||
a.rounded_full,
|
||||
{height: 7, width: 7},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[
|
||||
a.text_sm,
|
||||
item.unread ? a.font_bold : t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
{item.lastMessage}
|
||||
</Text>
|
||||
</View>
|
||||
</Link>
|
||||
)
|
||||
}}
|
||||
keyExtractor={item => item.profile.did}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={item => item.id}
|
||||
refreshing={isPTRing}
|
||||
onRefresh={onRefresh}
|
||||
onEndReached={onEndReached}
|
||||
ListHeaderComponent={
|
||||
<DesktopHeader
|
||||
newChatControl={newChatControl}
|
||||
onNavigateToSettings={onNavigateToSettings}
|
||||
/>
|
||||
}
|
||||
ListFooterComponent={
|
||||
<ListFooter
|
||||
isFetchingNextPage={isFetchingNextPage}
|
||||
|
@ -180,61 +158,139 @@ export function MessagesListScreen({navigation}: Props) {
|
|||
onEndReachedThreshold={3}
|
||||
initialNumToRender={initialNumToRender}
|
||||
windowSize={11}
|
||||
// @ts-ignore our .web version only -sfn
|
||||
desktopFixedHeight
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
function usePlaceholderConversations() {
|
||||
function ChatListItem({chat}: {chat: TempDmChatDefs.ChatView}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {getAgent} = useAgent()
|
||||
|
||||
return useInfiniteQuery({
|
||||
queryKey: ['messages'],
|
||||
queryFn: async () => {
|
||||
const people = await getAgent().getProfiles({actors: PLACEHOLDER_PEOPLE})
|
||||
return people.data.profiles.map(profile => ({
|
||||
profile,
|
||||
unread: Math.random() > 0.5,
|
||||
lastMessage: getRandomPost(),
|
||||
}))
|
||||
},
|
||||
initialPageParam: undefined,
|
||||
getNextPageParam: () => undefined,
|
||||
})
|
||||
}
|
||||
|
||||
const PLACEHOLDER_PEOPLE = [
|
||||
'pfrazee.com',
|
||||
'haileyok.com',
|
||||
'danabra.mov',
|
||||
'esb.lol',
|
||||
'samuel.bsky.team',
|
||||
]
|
||||
|
||||
function getRandomPost() {
|
||||
const num = Math.floor(Math.random() * 10)
|
||||
switch (num) {
|
||||
case 0:
|
||||
return 'hello'
|
||||
case 1:
|
||||
return 'lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua'
|
||||
case 2:
|
||||
return 'banger post'
|
||||
case 3:
|
||||
return 'lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua'
|
||||
case 4:
|
||||
return 'lol look at this bug'
|
||||
case 5:
|
||||
return 'wow'
|
||||
case 6:
|
||||
return "that's pretty cool, wow!"
|
||||
case 7:
|
||||
return 'I think this is a bug'
|
||||
case 8:
|
||||
return 'Hello World!'
|
||||
case 9:
|
||||
return 'DMs when???'
|
||||
default:
|
||||
return 'this is unlikely'
|
||||
let lastMessage = _(msg`No messages yet`)
|
||||
if (TempDmChatDefs.isMessageView(chat.lastMessage)) {
|
||||
lastMessage = chat.lastMessage.text
|
||||
}
|
||||
|
||||
const otherUser = chat.members.find(
|
||||
member => member.did !== getAgent().session?.did,
|
||||
)
|
||||
|
||||
if (!otherUser) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Link to={`/messages/${chat.id}`} style={a.flex_1}>
|
||||
{({hovered, pressed}) => (
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.flex_1,
|
||||
a.pl_md,
|
||||
a.py_sm,
|
||||
a.gap_md,
|
||||
a.pr_2xl,
|
||||
(hovered || pressed) && t.atoms.bg_contrast_25,
|
||||
]}>
|
||||
<View pointerEvents="none">
|
||||
<PreviewableUserAvatar profile={otherUser} size={42} />
|
||||
</View>
|
||||
<View style={[a.flex_1]}>
|
||||
<Text numberOfLines={1} style={a.leading_snug}>
|
||||
<Text style={[t.atoms.text, chat.unreadCount > 0 && a.font_bold]}>
|
||||
{otherUser.displayName || otherUser.handle}
|
||||
</Text>{' '}
|
||||
<Text style={t.atoms.text_contrast_medium}>
|
||||
@{otherUser.handle}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[
|
||||
a.text_sm,
|
||||
chat.unread ? a.font_bold : t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
{lastMessage}
|
||||
</Text>
|
||||
</View>
|
||||
{chat.unreadCount > 0 && (
|
||||
<View
|
||||
style={[
|
||||
a.flex_0,
|
||||
a.ml_2xl,
|
||||
a.mt_xs,
|
||||
{backgroundColor: t.palette.primary_500},
|
||||
a.rounded_full,
|
||||
{height: 7, width: 7},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
function DesktopHeader({
|
||||
newChatControl,
|
||||
onNavigateToSettings,
|
||||
}: {
|
||||
newChatControl: DialogControlProps
|
||||
onNavigateToSettings: () => void
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {gtMobile, gtTablet} = useBreakpoints()
|
||||
|
||||
if (!gtMobile) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
t.atoms.bg,
|
||||
t.atoms.border_contrast_low,
|
||||
a.border_b,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_between,
|
||||
a.gap_lg,
|
||||
a.px_lg,
|
||||
a.py_sm,
|
||||
]}>
|
||||
<Text style={[a.text_2xl, a.font_bold]}>
|
||||
<Trans>Messages</Trans>
|
||||
</Text>
|
||||
<View style={[a.flex_row, a.align_center, a.gap_md]}>
|
||||
<Button
|
||||
label={_(msg`Message settings`)}
|
||||
color="secondary"
|
||||
size="large"
|
||||
variant="ghost"
|
||||
style={[{height: 'auto', width: 'auto'}, a.px_sm, a.py_sm]}
|
||||
onPress={onNavigateToSettings}>
|
||||
<ButtonIcon icon={SettingsSlider} />
|
||||
</Button>
|
||||
{gtTablet && (
|
||||
<Button
|
||||
label={_(msg`New chat`)}
|
||||
color="primary"
|
||||
size="large"
|
||||
variant="solid"
|
||||
style={[{height: 'auto', width: 'auto'}, a.px_md, a.py_sm]}
|
||||
onPress={newChatControl.open}>
|
||||
<ButtonIcon icon={Envelope} position="right" />
|
||||
<ButtonText>
|
||||
<Trans>New chat</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
import {
|
||||
useInfiniteQuery,
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {useAgent} from '#/state/session'
|
||||
import * as TempDmChatDefs from '#/temp/dm/defs'
|
||||
|
@ -6,6 +11,7 @@ import * as TempDmChatGetChat from '#/temp/dm/getChat'
|
|||
import * as TempDmChatGetChatForMembers from '#/temp/dm/getChatForMembers'
|
||||
import * as TempDmChatGetChatLog from '#/temp/dm/getChatLog'
|
||||
import * as TempDmChatGetChatMessages from '#/temp/dm/getChatMessages'
|
||||
import * as TempDmChatListChats from '#/temp/dm/listChats'
|
||||
import {useDmServiceUrlStorage} from '../useDmServiceUrlStorage'
|
||||
|
||||
/**
|
||||
|
@ -250,3 +256,26 @@ export function useGetChatFromMembers({
|
|||
onError,
|
||||
})
|
||||
}
|
||||
|
||||
export function useListChats() {
|
||||
const headers = useHeaders()
|
||||
const {serviceUrl} = useDmServiceUrlStorage()
|
||||
|
||||
return useInfiniteQuery({
|
||||
queryKey: ['chats'],
|
||||
queryFn: async ({pageParam}) => {
|
||||
const response = await fetch(
|
||||
`${serviceUrl}/xrpc/temp.dm.listChats${
|
||||
pageParam ? `?cursor=${pageParam}` : ''
|
||||
}`,
|
||||
{headers},
|
||||
)
|
||||
|
||||
if (!response.ok) throw new Error('Failed to fetch chats')
|
||||
|
||||
return (await response.json()) as TempDmChatListChats.OutputSchema
|
||||
},
|
||||
initialPageParam: undefined as string | undefined,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -122,16 +122,6 @@ export function BottomBarWeb() {
|
|||
)
|
||||
}}
|
||||
</NavItem>
|
||||
<NavItem routeName="Messages" href="/messages">
|
||||
{() => {
|
||||
return (
|
||||
<Envelope
|
||||
size="lg"
|
||||
style={[styles.ctrlIcon, pal.text, styles.messagesIcon]}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</NavItem>
|
||||
{gate('dms') && (
|
||||
<NavItem routeName="Messages" href="/messages">
|
||||
{({isActive}) => {
|
||||
|
|
Loading…
Reference in New Issue