nostros/frontend/Pages/ConversationPage/index.tsx
2023-01-08 20:06:27 +00:00

286 lines
8.5 KiB
TypeScript

import React, { useContext, useEffect, useRef, useState } from 'react'
import { Button, Input, Layout, Text, TopNavigation, useTheme } from '@ui-kitten/components'
import { ScrollView, StyleSheet, TouchableOpacity } from 'react-native'
import { AppContext } from '../../Contexts/AppContext'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
import { EventKind, Event } from '../../lib/nostr/Events'
import {
DirectMessage,
getDirectMessages,
updateConversationRead,
} from '../../Functions/DatabaseFunctions/DirectMessages'
import { getUser, User } from '../../Functions/DatabaseFunctions/Users'
import Avatar from '../../Components/Avatar'
import Icon from 'react-native-vector-icons/FontAwesome5'
import { useTranslation } from 'react-i18next'
import { showMessage } from 'react-native-flash-message'
import { username, usersToTags } from '../../Functions/RelayFunctions/Users'
import moment from 'moment'
import TextContent from '../../Components/TextContent'
import { encrypt, decrypt } from '../../lib/nostr/Nip04'
export const ConversationPage: React.FC = () => {
const theme = useTheme()
const scrollViewRef = useRef<ScrollView>()
const { database, getActualPage, goBack, goToPage } = useContext(AppContext)
const { relayPool, publicKey, lastEventId, privateKey } = useContext(RelayPoolContext)
const conversationId = getActualPage().split('#')[1]
const otherPubKey = getActualPage().split('#')[2]
// State
const [directMessages, setDirectMessages] = useState<DirectMessage[]>([])
const [sendingMessages, setSendingMessages] = useState<DirectMessage[]>([])
const [otherUser, setOtherUser] = useState<User>({ id: otherPubKey })
const [user, setUser] = useState<User>()
const [input, setInput] = useState<string>('')
const { t } = useTranslation('common')
useEffect(() => {
loadDirectMessages()
}, [lastEventId])
useEffect(() => {
loadDirectMessages()
subscribeDirectMessages()
if (database && publicKey) {
getUser(publicKey, database).then((user) => {
if (user) setUser(user)
})
}
}, [])
const loadDirectMessages: () => void = () => {
if (database && publicKey) {
getUser(otherPubKey, database).then((user) => {
if (user) setOtherUser(user)
})
getDirectMessages(database, { conversationId, order: 'ASC' }).then((results) => {
if (results && results.length > 0) {
setSendingMessages([])
setDirectMessages(
results.map((message) => {
message.content = decrypt(privateKey, otherPubKey, message.content ?? '')
return message
}),
)
updateConversationRead(conversationId, database)
}
})
}
}
const subscribeDirectMessages: () => void = async () => {
relayPool?.unsubscribeAll()
if (publicKey && otherPubKey) {
relayPool?.subscribe('main-channel', {
kinds: [EventKind.directMessage],
authors: [publicKey],
'#p': [otherPubKey],
})
relayPool?.subscribe('main-channel', {
kinds: [EventKind.directMessage],
authors: [otherPubKey],
'#p': [publicKey],
})
}
}
const onPressOtherUser: () => void = () => {
goToPage(`profile#${otherPubKey}`)
}
const send: () => void = () => {
if (input !== '' && otherPubKey && publicKey && privateKey) {
const event: Event = {
content: input,
created_at: moment().unix(),
kind: EventKind.directMessage,
pubkey: publicKey,
tags: usersToTags([otherUser]),
}
setSendingMessages((prev) => [...prev, event as DirectMessage])
setInput('')
const encryptedcontent = encrypt(privateKey, otherPubKey, input)
encrypt(privateKey, otherPubKey, input).then((content) => {
relayPool
?.sendEvent({
...event,
content: encryptedcontent,
})
.catch((err) => {
showMessage({
message: t('alerts.privateMessageSendError'),
description: err.message,
type: 'danger',
})
})
})
.catch((err) => {
showMessage({
message: t('alerts.privateMessageEncryptError'),
description: err.message,
type: 'danger',
})
})
}
}
const userCard: (message: DirectMessage, index: number) => JSX.Element = (message, index) => {
if (!publicKey || !privateKey || !otherUser || !user) return <></>
return message.pubkey === otherPubKey ? (
<Layout key={index} style={styles.card} level='3'>
<Layout style={styles.layout} level='3'>
<Layout style={styles.profile} level='3'>
<TouchableOpacity onPress={onPressOtherUser}>
<Avatar name={otherUser.name} src={otherUser.picture} pubKey={otherUser.id} />
</TouchableOpacity>
</Layout>
<Layout style={styles.contentLeft} level='3'>
<Layout style={styles.title} level='3'>
<Text appearance='hint'>{username(otherUser)}</Text>
<Text appearance='hint'>
{moment.unix(message.created_at).format('HH:mm DD-MM-YY')}
</Text>
</Layout>
<TextContent event={message} />
</Layout>
</Layout>
</Layout>
) : (
<Layout key={index} style={styles.card} level='2'>
<Layout style={styles.contentRight} level='2'>
<Layout style={styles.title} level='2'>
<Text appearance='hint'>
{moment.unix(message.created_at).format('HH:mm DD-MM-YY')}
</Text>
<Layout style={styles.username} level='2'>
<Text appearance='hint'>{username(user)}</Text>
{message.id ? (
<></>
) : (
<Icon name='clock' size={12} color={theme['text-basic-color']} />
)}
</Layout>
</Layout>
<TextContent event={message} />
</Layout>
</Layout>
)
}
const onPressBack: () => void = () => {
relayPool?.unsubscribeAll()
goBack()
}
const renderBackAction = (): JSX.Element => {
return (
<Button
accessoryRight={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
onPress={onPressBack}
appearance='ghost'
/>
)
}
const styles = StyleSheet.create({
card: {
flex: 1,
padding: 20,
},
container: {
flex: 1,
paddingBottom: 85,
},
layout: {
flex: 1,
flexDirection: 'row',
},
username: {
flex: 1,
flexDirection: 'row',
justifyContent: 'flex-end',
},
profile: {
flex: 1,
width: 38,
alignItems: 'center',
backgroundColor: 'transparent',
},
contentLeft: {
flex: 4,
},
contentRight: {
flex: 4,
alignItems: 'flex-end',
},
form: {
flex: 1,
padding: 15,
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
bottom: 0,
width: '100%',
flexDirection: 'row',
},
input: {
flex: 5,
},
sendButton: {
flex: 1,
paddingLeft: 15,
},
title: {
flexDirection: 'row',
width: '100%',
justifyContent: 'space-between',
},
})
return (
<>
<TopNavigation
alignment='center'
title={
<TouchableOpacity onPress={onPressOtherUser}>
<Text>{username(otherUser)}</Text>
</TouchableOpacity>
}
accessoryLeft={renderBackAction}
/>
<Layout style={styles.container}>
<ScrollView
ref={scrollViewRef}
onContentSizeChange={() => scrollViewRef.current?.scrollToEnd({ animated: true })}
horizontal={false}
>
{[...directMessages, ...sendingMessages].map((msg, index) => userCard(msg, index))}
</ScrollView>
</Layout>
<Layout style={styles.form}>
<Layout style={styles.input}>
<Input
multiline={true}
value={input}
onChangeText={setInput}
size='large'
keyboardType='twitter'
onPressOut={() => scrollViewRef.current?.scrollToEnd({ animated: true })}
/>
</Layout>
<Layout style={styles.sendButton}>
<Button onPress={send}>
<Icon name={'paper-plane'} size={22} color={theme['text-basic-color']} solid />
</Button>
</Layout>
</Layout>
</>
)
}
export default ConversationPage