[Clipclops] New clipclop dialog (#3750)
* add new routes with placeholder screens * add clops list * add a clop input * add some better padding to the clops * some more adjustments * add rnkc * implement rnkc * implement rnkc * be a little less weird about it * rename clop stuff * rename more clop * one more * add codegenerated lexicon * replace hailey's types * use codegen'd types in components * fix error + throw if fetch failed * remove bad imports * update messageslist and messageitem * import useState * replace hailey's types * use codegen'd types in components * add FAB * new chat dialog * error + default search term * fix typo * fix web styles * optimistically set chat data * use cursor instead of last rev * [Clipclops] Temp codegenerated lexicon (#3749) * add codegenerated lexicon * replace hailey's types * use codegen'd types in components * fix error + throw if fetch failed * remove bad imports * update messageslist and messageitem * import useState * add clop service URL hook * add dm service url storage * use context * use context for service url (temp) * remove log * cleanup merge * fix merge error * disable hack * sender-based message styles * temporary filter * merge cleanup * add `hideBackButton` * rm unneeded return * tried to be smart * hide go back button * use `searchActorTypeahead` instead --------- Co-authored-by: Hailey <me@haileyok.com>
This commit is contained in:
parent
2b7d796ca9
commit
bcd3678067
8 changed files with 352 additions and 56 deletions
|
@ -1,12 +1,16 @@
|
|||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {useAgent} from '#/state/session'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Text} from '#/components/Typography'
|
||||
import * as TempDmChatDefs from '#/temp/dm/defs'
|
||||
|
||||
export function MessageItem({item}: {item: TempDmChatDefs.MessageView}) {
|
||||
const t = useTheme()
|
||||
const {getAgent} = useAgent()
|
||||
|
||||
const fromMe = item.sender?.did === getAgent().session?.did
|
||||
|
||||
return (
|
||||
<View
|
||||
|
@ -15,13 +19,17 @@ export function MessageItem({item}: {item: TempDmChatDefs.MessageView}) {
|
|||
a.px_md,
|
||||
a.my_xs,
|
||||
a.rounded_md,
|
||||
fromMe ? a.self_end : a.self_start,
|
||||
{
|
||||
backgroundColor: t.palette.primary_500,
|
||||
backgroundColor: fromMe
|
||||
? t.palette.primary_500
|
||||
: t.palette.contrast_50,
|
||||
maxWidth: '65%',
|
||||
borderRadius: 17,
|
||||
},
|
||||
]}>
|
||||
<Text style={[a.text_md, {lineHeight: 1.2, color: 'white'}]}>
|
||||
<Text
|
||||
style={[a.text_md, a.leading_snug, fromMe && {color: t.palette.white}]}>
|
||||
{item.text}
|
||||
</Text>
|
||||
</View>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React, {useCallback, useMemo, useRef, useState} from 'react'
|
||||
import {Alert, FlatList, View, ViewToken} from 'react-native'
|
||||
import {FlatList, View, ViewToken} from 'react-native'
|
||||
import {Alert} from 'react-native'
|
||||
import {KeyboardAvoidingView} from 'react-native-keyboard-controller'
|
||||
|
||||
import {isWeb} from 'platform/detection'
|
||||
|
@ -64,6 +65,7 @@ export function MessagesList({chatId}: {chatId: string}) {
|
|||
const totalMessages = useRef(10)
|
||||
|
||||
// TODO later
|
||||
|
||||
const [_, setShowSpinner] = useState(false)
|
||||
|
||||
// Query Data
|
||||
|
@ -147,6 +149,8 @@ export function MessagesList({chatId}: {chatId: string}) {
|
|||
},
|
||||
)
|
||||
totalMessages.current = filtered.length
|
||||
|
||||
return filtered
|
||||
}, [chat])
|
||||
|
||||
return (
|
||||
|
@ -162,7 +166,7 @@ export function MessagesList({chatId}: {chatId: string}) {
|
|||
contentContainerStyle={{paddingHorizontal: 10}}
|
||||
// In the future, we might want to adjust this value. Not very concerning right now as long as we are only
|
||||
// dealing with text. But whenever we have images or other media and things are taller, we will want to lower
|
||||
// this...probably
|
||||
// this...probably.
|
||||
initialNumToRender={20}
|
||||
// Same with the max to render per batch. Let's be safe for now though.
|
||||
maxToRenderPerBatch={25}
|
||||
|
@ -175,7 +179,6 @@ export function MessagesList({chatId}: {chatId: string}) {
|
|||
maintainVisibleContentPosition={{
|
||||
minIndexForVisible: 0,
|
||||
}}
|
||||
// This is actually a header since we are inverted!
|
||||
ListFooterComponent={<MaybeLoader isLoading={false} />}
|
||||
removeClippedSubviews={true}
|
||||
ref={flatListRef}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, {useCallback, useState} from 'react'
|
||||
import React, {useCallback, useMemo, useState} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
@ -20,10 +20,11 @@ import {SettingsSliderVertical_Stroke2_Corner0_Rounded as SettingsSlider} from '
|
|||
import {Link} from '#/components/Link'
|
||||
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {NewChat} from '../../../components/dms/NewChat'
|
||||
import {ClipClopGate} from '../gate'
|
||||
|
||||
type Props = NativeStackScreenProps<MessagesTabNavigatorParams, 'MessagesList'>
|
||||
export function MessagesListScreen({}: Props) {
|
||||
export function MessagesListScreen({navigation}: Props) {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
|
||||
|
@ -53,14 +54,14 @@ export function MessagesListScreen({}: Props) {
|
|||
|
||||
const isError = !!error
|
||||
|
||||
const conversations = React.useMemo(() => {
|
||||
const conversations = useMemo(() => {
|
||||
if (data?.pages) {
|
||||
return data.pages.flat()
|
||||
}
|
||||
return []
|
||||
}, [data])
|
||||
|
||||
const onRefresh = React.useCallback(async () => {
|
||||
const onRefresh = useCallback(async () => {
|
||||
setIsPTRing(true)
|
||||
try {
|
||||
await refetch()
|
||||
|
@ -70,7 +71,7 @@ export function MessagesListScreen({}: Props) {
|
|||
setIsPTRing(false)
|
||||
}, [refetch, setIsPTRing])
|
||||
|
||||
const onEndReached = React.useCallback(async () => {
|
||||
const onEndReached = useCallback(async () => {
|
||||
if (isFetchingNextPage || !hasNextPage || isError) return
|
||||
try {
|
||||
await fetchNextPage()
|
||||
|
@ -79,26 +80,35 @@ export function MessagesListScreen({}: Props) {
|
|||
}
|
||||
}, [isFetchingNextPage, hasNextPage, isError, fetchNextPage])
|
||||
|
||||
const onNewChat = useCallback(
|
||||
(conversation: string) =>
|
||||
navigation.navigate('MessagesConversation', {conversation}),
|
||||
[navigation],
|
||||
)
|
||||
|
||||
const gate = useGate()
|
||||
if (!gate('dms')) return <ClipClopGate />
|
||||
|
||||
if (conversations.length < 1) {
|
||||
return (
|
||||
<ListMaybePlaceholder
|
||||
isLoading={isLoading}
|
||||
isError={isError}
|
||||
emptyType="results"
|
||||
emptyMessage={_(
|
||||
msg`You have no messages yet. Start a conversation with someone!`,
|
||||
)}
|
||||
errorMessage={cleanError(error)}
|
||||
onRetry={isError ? refetch : undefined}
|
||||
/>
|
||||
<>
|
||||
<ListMaybePlaceholder
|
||||
isLoading={isLoading}
|
||||
isError={isError}
|
||||
emptyType="results"
|
||||
emptyMessage={_(
|
||||
msg`You have no messages yet. Start a conversation with someone!`,
|
||||
)}
|
||||
errorMessage={cleanError(error)}
|
||||
onRetry={isError ? refetch : undefined}
|
||||
/>
|
||||
<NewChat onNewChat={onNewChat} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View style={a.flex_1}>
|
||||
<ViewHeader
|
||||
title={_(msg`Messages`)}
|
||||
showOnDesktop
|
||||
|
@ -106,6 +116,7 @@ export function MessagesListScreen({}: Props) {
|
|||
showBorder
|
||||
canGoBack={false}
|
||||
/>
|
||||
<NewChat onNewChat={onNewChat} />
|
||||
<List
|
||||
data={conversations}
|
||||
renderItem={({item}) => {
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {useSession} from 'state/session'
|
||||
import {useDmServiceUrlStorage} from '#/screens/Messages/Temp/useDmServiceUrlStorage'
|
||||
import {useAgent} from '#/state/session'
|
||||
import * as TempDmChatDefs from '#/temp/dm/defs'
|
||||
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 {useDmServiceUrlStorage} from '../useDmServiceUrlStorage'
|
||||
|
||||
/**
|
||||
* TEMPORARY, PLEASE DO NOT JUDGE ME REACT QUERY OVERLORDS 🙏
|
||||
* (and do not try this at home)
|
||||
*/
|
||||
|
||||
function createHeaders(did: string) {
|
||||
const useHeaders = () => {
|
||||
const {getAgent} = useAgent()
|
||||
return {
|
||||
Authorization: did,
|
||||
get Authorization() {
|
||||
return getAgent().session!.did
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,10 +31,8 @@ type Chat = {
|
|||
|
||||
export function useChat(chatId: string) {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const headers = useHeaders()
|
||||
const {serviceUrl} = useDmServiceUrlStorage()
|
||||
const {currentAccount} = useSession()
|
||||
const did = currentAccount?.did ?? ''
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['chat', chatId],
|
||||
|
@ -44,7 +46,7 @@ export function useChat(chatId: string) {
|
|||
const messagesResponse = await fetch(
|
||||
`${serviceUrl}/xrpc/temp.dm.getChatMessages?chatId=${chatId}`,
|
||||
{
|
||||
headers: createHeaders(did),
|
||||
headers,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -56,7 +58,7 @@ export function useChat(chatId: string) {
|
|||
const chatResponse = await fetch(
|
||||
`${serviceUrl}/xrpc/temp.dm.getChat?chatId=${chatId}`,
|
||||
{
|
||||
headers: createHeaders(did),
|
||||
headers,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -90,10 +92,8 @@ export function createTempId() {
|
|||
|
||||
export function useSendMessageMutation(chatId: string) {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const headers = useHeaders()
|
||||
const {serviceUrl} = useDmServiceUrlStorage()
|
||||
const {currentAccount} = useSession()
|
||||
const did = currentAccount?.did ?? ''
|
||||
|
||||
return useMutation<
|
||||
TempDmChatDefs.Message,
|
||||
|
@ -108,7 +108,7 @@ export function useSendMessageMutation(chatId: string) {
|
|||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...createHeaders(did),
|
||||
...headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
|
@ -130,8 +130,10 @@ export function useSendMessageMutation(chatId: string) {
|
|||
...prev,
|
||||
messages: [
|
||||
{
|
||||
$type: 'temp.dm.defs#messageView',
|
||||
id: variables.tempId,
|
||||
text: variables.message,
|
||||
sender: {did: headers.Authorization}, // TODO a real DID get
|
||||
},
|
||||
...prev.messages,
|
||||
],
|
||||
|
@ -165,10 +167,8 @@ export function useSendMessageMutation(chatId: string) {
|
|||
|
||||
export function useChatLogQuery() {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const headers = useHeaders()
|
||||
const {serviceUrl} = useDmServiceUrlStorage()
|
||||
const {currentAccount} = useSession()
|
||||
const did = currentAccount?.did ?? ''
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['chatLog'],
|
||||
|
@ -183,7 +183,7 @@ export function useChatLogQuery() {
|
|||
prevLog?.cursor ?? ''
|
||||
}`,
|
||||
{
|
||||
headers: createHeaders(did),
|
||||
headers,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -193,13 +193,10 @@ export function useChatLogQuery() {
|
|||
(await response.json()) as TempDmChatGetChatLog.OutputSchema
|
||||
|
||||
for (const log of json.logs) {
|
||||
if (TempDmChatDefs.isLogDeleteMessage(log)) {
|
||||
if (TempDmChatDefs.isLogCreateMessage(log)) {
|
||||
queryClient.setQueryData(['chat', log.chatId], (prev: Chat) => {
|
||||
// What to do in this case
|
||||
if (!prev) return
|
||||
|
||||
// HACK we don't know who the creator of a message is, so just filter by id for now
|
||||
if (prev.messages.find(m => m.id === log.message.id)) return prev
|
||||
// TODO hack filter out duplicates
|
||||
if (prev?.messages.find(m => m.id === log.message.id)) return
|
||||
|
||||
return {
|
||||
...prev,
|
||||
|
@ -217,3 +214,39 @@ export function useChatLogQuery() {
|
|||
refetchInterval: 5000,
|
||||
})
|
||||
}
|
||||
|
||||
export function useGetChatFromMembers({
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess?: (data: TempDmChatGetChatForMembers.OutputSchema) => void
|
||||
onError?: (error: Error) => void
|
||||
}) {
|
||||
const queryClient = useQueryClient()
|
||||
const headers = useHeaders()
|
||||
const {serviceUrl} = useDmServiceUrlStorage()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (members: string[]) => {
|
||||
const response = await fetch(
|
||||
`${serviceUrl}/xrpc/temp.dm.getChatForMembers?members=${members.join(
|
||||
',',
|
||||
)}`,
|
||||
{headers},
|
||||
)
|
||||
|
||||
if (!response.ok) throw new Error('Failed to fetch chat')
|
||||
|
||||
return (await response.json()) as TempDmChatGetChatForMembers.OutputSchema
|
||||
},
|
||||
onSuccess: data => {
|
||||
queryClient.setQueryData(['chat', data.chat.id], {
|
||||
chatId: data.chat.id,
|
||||
messages: [],
|
||||
lastRev: data.chat.rev,
|
||||
})
|
||||
onSuccess?.(data)
|
||||
},
|
||||
onError,
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue