mirror of
https://github.com/KoalaSat/nostros.git
synced 2024-09-29 14:40:43 +00:00
New UI navigation bar and profile config (#120)
This commit is contained in:
commit
28f404983f
@ -1,48 +1,20 @@
|
||||
import React from 'react'
|
||||
import { Layout, useTheme } from '@ui-kitten/components'
|
||||
import { Image, StyleSheet, Text } from 'react-native'
|
||||
import { stringToColour } from '../../Functions/NativeFunctions'
|
||||
import { StyleSheet } from 'react-native'
|
||||
import { Avatar as PaperAvatar, useTheme } from 'react-native-paper'
|
||||
|
||||
interface AvatarProps {
|
||||
pubKey: string
|
||||
src?: string
|
||||
name?: string
|
||||
pubKey: string
|
||||
size?: number
|
||||
lud06?: string
|
||||
}
|
||||
|
||||
export const Avatar: React.FC<AvatarProps> = ({ src, name, pubKey, size = 50 }) => {
|
||||
export const NostrosAvatar: React.FC<AvatarProps> = ({ src, name, pubKey, size = 40, lud06 }) => {
|
||||
const theme = useTheme()
|
||||
const displayName = name && name !== '' ? name : pubKey
|
||||
const styles = StyleSheet.create({
|
||||
layout: {
|
||||
flexDirection: 'row',
|
||||
alignContent: 'center',
|
||||
width: size,
|
||||
height: size,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
image: {
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: 100,
|
||||
},
|
||||
textAvatarLayout: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: 100,
|
||||
backgroundColor: stringToColour(pubKey),
|
||||
},
|
||||
textAvatar: {
|
||||
fontSize: size / 2,
|
||||
alignContent: 'center',
|
||||
color: theme['text-basic-color'],
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
})
|
||||
|
||||
const hasLud06 = lud06 && lud06 !== ''
|
||||
const lud06IconSize = size / 2.85
|
||||
const validImage: () => boolean = () => {
|
||||
if (src) {
|
||||
const regexp = /^(https?:\/\/.*\.(?:png|jpg|jpeg))$/
|
||||
@ -53,16 +25,30 @@ export const Avatar: React.FC<AvatarProps> = ({ src, name, pubKey, size = 50 })
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout style={styles.layout}>
|
||||
<>
|
||||
{validImage() ? (
|
||||
<Image style={styles.image} source={{ uri: src }} />
|
||||
<PaperAvatar.Image size={size} source={{ uri: src }} />
|
||||
) : (
|
||||
<Layout style={styles.textAvatarLayout}>
|
||||
<Text style={styles.textAvatar}>{displayName.substring(0, 2)}</Text>
|
||||
</Layout>
|
||||
<PaperAvatar.Text size={size} label={displayName} />
|
||||
)}
|
||||
</Layout>
|
||||
{hasLud06 && (
|
||||
<PaperAvatar.Icon
|
||||
size={lud06IconSize}
|
||||
icon='lightning-bolt'
|
||||
style={[
|
||||
styles.iconLightning,
|
||||
{ right: -(size - lud06IconSize), backgroundColor: theme.colors.secondaryContainer, top: lud06IconSize * -1 },
|
||||
]}
|
||||
color='#F5D112'
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Avatar
|
||||
const styles = StyleSheet.create({
|
||||
iconLightning: {
|
||||
},
|
||||
})
|
||||
|
||||
export default NostrosAvatar
|
||||
|
@ -1,11 +1,12 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { requestInvoice } from 'lnurl-pay'
|
||||
import { Button, Card, Input, Layout, Modal, Text } from '@ui-kitten/components'
|
||||
import QRCode from 'react-native-qrcode-svg'
|
||||
import { Event } from '../../lib/nostr/Events'
|
||||
import { User } from '../../Functions/DatabaseFunctions/Users'
|
||||
import { Clipboard, Linking, StyleSheet } from 'react-native'
|
||||
import { Clipboard, Linking, StyleSheet, View } from 'react-native'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { showMessage } from 'react-native-flash-message'
|
||||
import RBSheet from 'react-native-raw-bottom-sheet'
|
||||
import { Button, Card, IconButton, Text, TextInput, useTheme } from 'react-native-paper'
|
||||
|
||||
interface TextContentProps {
|
||||
open: boolean
|
||||
@ -15,73 +16,41 @@ interface TextContentProps {
|
||||
}
|
||||
|
||||
export const LnPayment: React.FC<TextContentProps> = ({ open, setOpen, event, user }) => {
|
||||
const theme = useTheme()
|
||||
const { t } = useTranslation('common')
|
||||
const bottomSheetLnPaymentRef = React.useRef<RBSheet>(null)
|
||||
const bottomSheetInvoiceRef = React.useRef<RBSheet>(null)
|
||||
const [monto, setMonto] = useState<string>('')
|
||||
const defaultComment = event?.id ? `Tip for Nostr event ${event?.id}` : ''
|
||||
const [comment, setComment] = useState<string>(defaultComment)
|
||||
const [invoice, setInvoice] = useState<string>()
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
|
||||
useEffect(() => {
|
||||
setMonto('')
|
||||
setInvoice(undefined)
|
||||
if (open) {
|
||||
bottomSheetLnPaymentRef.current?.open()
|
||||
} else {
|
||||
bottomSheetLnPaymentRef.current?.close()
|
||||
bottomSheetInvoiceRef.current?.close()
|
||||
}
|
||||
}, [open])
|
||||
|
||||
useEffect(() => {
|
||||
setComment(defaultComment)
|
||||
}, [event, open])
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
modal: {
|
||||
paddingLeft: 32,
|
||||
paddingRight: 32,
|
||||
width: '100%',
|
||||
},
|
||||
backdrop: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
input: {
|
||||
marginTop: 31,
|
||||
},
|
||||
modalContainer: {
|
||||
marginBottom: 15,
|
||||
},
|
||||
buttonsContainer: {
|
||||
flexDirection: 'row',
|
||||
marginTop: 31,
|
||||
},
|
||||
buttonLeft: {
|
||||
flex: 3,
|
||||
paddingRight: 16,
|
||||
},
|
||||
buttonRight: {
|
||||
flex: 3,
|
||||
paddingLeft: 16,
|
||||
},
|
||||
buttonMonto: {
|
||||
flex: 2,
|
||||
},
|
||||
buttonMontoMiddle: {
|
||||
flex: 2,
|
||||
marginLeft: 10,
|
||||
marginRight: 10,
|
||||
},
|
||||
satoshi: {
|
||||
fontFamily: 'Satoshi-Symbol',
|
||||
},
|
||||
})
|
||||
|
||||
const copyInvoice: (invoice: string) => void = (invoice) => {
|
||||
Clipboard.setString(invoice)
|
||||
showMessage({
|
||||
message: t('alerts.invoiceCopied'),
|
||||
type: 'success',
|
||||
})
|
||||
const copyInvoice: () => void = () => {
|
||||
console.log(invoice)
|
||||
Clipboard.setString(invoice ?? '')
|
||||
}
|
||||
|
||||
const openApp: (invoice: string) => void = (invoice) => {
|
||||
const openApp: () => void = () => {
|
||||
Linking.openURL(`lightning:${invoice}`)
|
||||
}
|
||||
|
||||
const generateInvoice: (copy: boolean) => void = async (copy) => {
|
||||
const generateInvoice: () => void = async () => {
|
||||
if (user?.lnurl && monto !== '') {
|
||||
setLoading(true)
|
||||
requestInvoice({
|
||||
@ -91,98 +60,161 @@ export const LnPayment: React.FC<TextContentProps> = ({ open, setOpen, event, us
|
||||
})
|
||||
.then((action) => {
|
||||
if (action.hasValidAmount && action.invoice) {
|
||||
copy ? copyInvoice(action.invoice) : openApp(action.invoice)
|
||||
} else {
|
||||
showMessage({
|
||||
message: t('alerts.invoiceError'),
|
||||
type: 'danger',
|
||||
})
|
||||
setInvoice(action.invoice)
|
||||
bottomSheetInvoiceRef.current?.open()
|
||||
}
|
||||
setLoading(false)
|
||||
setOpen(false)
|
||||
setMonto('')
|
||||
setComment('')
|
||||
})
|
||||
.catch(() => setLoading(false))
|
||||
}
|
||||
}
|
||||
|
||||
const rbSheetCustomStyles = React.useMemo(() => {
|
||||
return {
|
||||
container: {
|
||||
...styles.rbsheetContainer,
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
draggableIcon: styles.rbsheetDraggableIcon,
|
||||
}
|
||||
}, [])
|
||||
|
||||
return user?.lnurl ? (
|
||||
<Modal
|
||||
style={styles.modal}
|
||||
visible={open}
|
||||
backdropStyle={styles.backdrop}
|
||||
onBackdropPress={() => setOpen(false)}
|
||||
>
|
||||
<Card disabled={true}>
|
||||
<Layout style={styles.modalContainer}>
|
||||
<Layout style={styles.buttonsContainer}>
|
||||
<Button style={styles.buttonMonto} onPress={() => setMonto('1000')}>
|
||||
<>
|
||||
<RBSheet
|
||||
ref={bottomSheetLnPaymentRef}
|
||||
closeOnDragDown={true}
|
||||
height={330}
|
||||
customStyles={rbSheetCustomStyles}
|
||||
onClose={() => setOpen(false)}
|
||||
>
|
||||
<View>
|
||||
<View style={styles.montoSelection}>
|
||||
<Button style={styles.montoButton} mode='outlined' onPress={() => setMonto('1000')}>
|
||||
<>
|
||||
<Text style={styles.satoshi}>s</Text>
|
||||
<Text> 1k</Text>
|
||||
</>
|
||||
</Button>
|
||||
<Button style={styles.buttonMontoMiddle} onPress={() => setMonto('5000')}>
|
||||
<Button style={styles.montoButton} mode='outlined' onPress={() => setMonto('5000')}>
|
||||
<>
|
||||
<Text style={styles.satoshi}>s</Text>
|
||||
<Text> 5k</Text>
|
||||
</>
|
||||
</Button>
|
||||
<Button style={styles.buttonMonto} onPress={() => setMonto('10000')}>
|
||||
<Button style={styles.montoButton} mode='outlined' onPress={() => setMonto('10000')}>
|
||||
<>
|
||||
<Text style={styles.satoshi}>s</Text>
|
||||
<Text> 10k</Text>
|
||||
</>
|
||||
</Button>
|
||||
</Layout>
|
||||
<Layout style={styles.input}>
|
||||
<Input
|
||||
value={monto}
|
||||
onChangeText={(text) => {
|
||||
if (/^\d+$/.test(text)) {
|
||||
setMonto(text)
|
||||
}
|
||||
}}
|
||||
size='large'
|
||||
placeholder={t('lnPayment.monto')}
|
||||
accessoryLeft={() => <Text style={styles.satoshi}>s</Text>}
|
||||
/>
|
||||
</Layout>
|
||||
<Layout style={styles.input}>
|
||||
<Input
|
||||
value={comment}
|
||||
onChangeText={setComment}
|
||||
placeholder={t('lnPayment.comment')}
|
||||
size='large'
|
||||
/>
|
||||
</Layout>
|
||||
<Layout style={styles.buttonsContainer}>
|
||||
<Layout style={styles.buttonLeft}>
|
||||
<Button
|
||||
onPress={() => generateInvoice(true)}
|
||||
appearance='ghost'
|
||||
disabled={loading || monto === ''}
|
||||
>
|
||||
{t('lnPayment.copy')}
|
||||
</Button>
|
||||
</Layout>
|
||||
<Layout style={styles.buttonRight}>
|
||||
<Button
|
||||
onPress={() => generateInvoice(false)}
|
||||
status='warning'
|
||||
disabled={loading || monto === ''}
|
||||
>
|
||||
{t('lnPayment.openApp')}
|
||||
</Button>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</Card>
|
||||
</Modal>
|
||||
</View>
|
||||
<TextInput
|
||||
mode='outlined'
|
||||
label={t('lnPayment.monto') ?? ''}
|
||||
onChangeText={setMonto}
|
||||
value={monto}
|
||||
/>
|
||||
<TextInput
|
||||
mode='outlined'
|
||||
label={t('lnPayment.comment') ?? ''}
|
||||
onChangeText={setComment}
|
||||
value={comment}
|
||||
/>
|
||||
<Button
|
||||
mode='contained'
|
||||
disabled={loading || monto === ''}
|
||||
onPress={() => generateInvoice()}
|
||||
>
|
||||
{t('lnPayment.generateInvoice')}
|
||||
</Button>
|
||||
<Button mode='outlined' onPress={() => setOpen(false)}>
|
||||
{t('lnPayment.cancel')}
|
||||
</Button>
|
||||
</View>
|
||||
</RBSheet>
|
||||
<RBSheet
|
||||
ref={bottomSheetInvoiceRef}
|
||||
closeOnDragDown={true}
|
||||
height={630}
|
||||
customStyles={rbSheetCustomStyles}
|
||||
onClose={() => setOpen(false)}
|
||||
>
|
||||
<Card style={styles.qrContainer}>
|
||||
<Card.Content>
|
||||
<View>
|
||||
<QRCode value={invoice} size={350} />
|
||||
</View>
|
||||
<View style={styles.qrText}>
|
||||
<Text variant='titleMedium' style={styles.satoshi}>
|
||||
s
|
||||
</Text>
|
||||
<Text variant='titleMedium'>{monto}</Text>
|
||||
</View>
|
||||
{comment && (
|
||||
<View style={styles.qrText}>
|
||||
<Text>{comment}</Text>
|
||||
</View>
|
||||
)}
|
||||
</Card.Content>
|
||||
</Card>
|
||||
<View style={styles.cardActions}>
|
||||
<View style={styles.actionButton}>
|
||||
<IconButton icon='content-copy' size={28} onPress={copyInvoice} />
|
||||
<Text>{t('profileConfigPage.copyNPub')}</Text>
|
||||
</View>
|
||||
<View style={styles.actionButton}>
|
||||
<IconButton icon='wallet' size={28} onPress={openApp} />
|
||||
<Text>{t('profileConfigPage.invoice')}</Text>
|
||||
</View>
|
||||
<View style={styles.actionButton}></View>
|
||||
<View style={styles.actionButton}></View>
|
||||
</View>
|
||||
</RBSheet>
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
qrContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
qrText: {
|
||||
marginTop: 20,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
rbsheetDraggableIcon: {
|
||||
backgroundColor: '#000',
|
||||
},
|
||||
rbsheetContainer: {
|
||||
padding: 16,
|
||||
borderTopRightRadius: 28,
|
||||
borderTopLeftRadius: 28,
|
||||
},
|
||||
satoshi: {
|
||||
fontFamily: 'Satoshi-Symbol',
|
||||
fontSize: 20,
|
||||
},
|
||||
montoSelection: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
montoButton: {
|
||||
flex: 2,
|
||||
},
|
||||
actionButton: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: 80,
|
||||
},
|
||||
cardActions: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around',
|
||||
},
|
||||
})
|
||||
|
||||
export default LnPayment
|
||||
|
@ -1,84 +1,52 @@
|
||||
import * as React from 'react'
|
||||
import { StyleSheet } from 'react-native'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { DrawerContentScrollView } from '@react-navigation/drawer'
|
||||
import { Button, Drawer, Text, useTheme } from 'react-native-paper'
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Chip,
|
||||
Drawer,
|
||||
IconButton,
|
||||
Text,
|
||||
TouchableRipple,
|
||||
useTheme,
|
||||
} from 'react-native-paper'
|
||||
import Logo from '../Logo'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import SInfo from 'react-native-sensitive-info'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { dropTables } from '../../Functions/DatabaseFunctions'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import { UserContext } from '../../Contexts/UserContext'
|
||||
import { DrawerNavigationHelpers } from '@react-navigation/drawer/lib/typescript/src/types'
|
||||
|
||||
interface ItemList {
|
||||
label: string
|
||||
icon: string
|
||||
key: number
|
||||
right?: () => JSX.Element
|
||||
}
|
||||
import { navigate } from '../../lib/Navigation'
|
||||
import NostrosAvatar from '../Avatar'
|
||||
import { formatPubKey } from '../../Functions/RelayFunctions/Users'
|
||||
|
||||
interface MenuItemsProps {
|
||||
navigation: DrawerNavigationHelpers;
|
||||
navigation: DrawerNavigationHelpers
|
||||
}
|
||||
|
||||
export const MenuItems: React.FC<MenuItemsProps> = ({ navigation }) => {
|
||||
const [drawerItemIndex, setDrawerItemIndex] = React.useState<number>(-1)
|
||||
const { goToPage, database, init } = React.useContext(AppContext)
|
||||
const { setPrivateKey, setPublicKey, relayPool, publicKey } = React.useContext(RelayPoolContext)
|
||||
const { relays } = React.useContext(RelayPoolContext)
|
||||
const { nPub, publicKey, user, contactsCount, followersCount, logout } =
|
||||
React.useContext(UserContext)
|
||||
const { t } = useTranslation('common')
|
||||
const theme = useTheme()
|
||||
|
||||
const onPressLogout: () => void = () => {
|
||||
if (database) {
|
||||
relayPool?.unsubscribeAll()
|
||||
setPrivateKey(undefined)
|
||||
setPublicKey(undefined)
|
||||
dropTables(database).then(() => {
|
||||
SInfo.deleteItem('privateKey', {}).then(() => {
|
||||
SInfo.deleteItem('publicKey', {}).then(() => {
|
||||
init()
|
||||
goToPage('landing', true)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
logout()
|
||||
}
|
||||
|
||||
const onPressItem: (index:number) => void = (index) => {
|
||||
const onPressItem: (key: string, index: number) => void = (key, index) => {
|
||||
setDrawerItemIndex(index)
|
||||
const pagesIndex = [
|
||||
'Relays',
|
||||
'Config',
|
||||
'About'
|
||||
]
|
||||
navigation.navigate(pagesIndex[index])
|
||||
}
|
||||
|
||||
const relaysRightButton: () => JSX.Element = () => {
|
||||
if (!relayPool || relayPool?.relays.length < 1) {
|
||||
return <Text style={{color: theme.colors.error}}>{t('menuItems.notConnected')}</Text>
|
||||
if (key === 'relays') {
|
||||
navigate('Relays')
|
||||
} else if (key === 'config') {
|
||||
navigate('Feed', { page: 'Config' })
|
||||
} else if (key === 'about') {
|
||||
navigate('About')
|
||||
}
|
||||
return <Text style={{color: theme.colors.inversePrimary}}>{t('menuItems.connectedRelays', { number: relayPool?.relays.length.toString()})}</Text>
|
||||
}
|
||||
|
||||
const DrawerItemsData = React.useMemo(
|
||||
() => {
|
||||
if (!publicKey) return []
|
||||
|
||||
const defaultList: ItemList[] = [
|
||||
{ label: t('menuItems.relays'), icon: 'message-question-outline', key: 0, right: relaysRightButton},
|
||||
{ label: t('menuItems.configuration'), icon: 'cog-outline', key: 1 }
|
||||
]
|
||||
|
||||
return defaultList
|
||||
},
|
||||
[publicKey],
|
||||
)
|
||||
const DrawerBottomItemsData = React.useMemo(
|
||||
() => [{ label: t('menuItems.about'), icon: 'message-question-outline', key: 2 }],
|
||||
[],
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<DrawerContentScrollView
|
||||
@ -94,30 +62,85 @@ export const MenuItems: React.FC<MenuItemsProps> = ({ navigation }) => {
|
||||
<Drawer.Section showDivider={false}>
|
||||
<Logo />
|
||||
</Drawer.Section>
|
||||
<Drawer.Section showDivider={publicKey !== undefined}>
|
||||
{DrawerItemsData.map((props, index) => (
|
||||
{nPub && (
|
||||
<Card style={styles.cardContainer}>
|
||||
<Card.Content style={styles.cardContent}>
|
||||
<TouchableRipple onPress={() => navigate('Profile')}>
|
||||
<View style={styles.cardContent}>
|
||||
<View style={styles.cardAvatar}>
|
||||
<NostrosAvatar
|
||||
name={user?.name}
|
||||
pubKey={nPub}
|
||||
src={user?.picture}
|
||||
lud06={user?.lnurl}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text variant='titleMedium'>{user?.name}</Text>
|
||||
<Text>{formatPubKey(nPub)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
<View style={styles.cardEdit}>
|
||||
<IconButton icon='pencil' size={20} onPress={() => navigate('ProfileConfig')} />
|
||||
</View>
|
||||
</Card.Content>
|
||||
<Card.Content style={styles.cardActions}>
|
||||
<Chip
|
||||
compact={true}
|
||||
style={styles.cardActionsChip}
|
||||
onPress={() => console.log('Pressed')}
|
||||
>
|
||||
{t('menuItems.following', { following: contactsCount })}
|
||||
</Chip>
|
||||
<Chip
|
||||
compact={true}
|
||||
style={styles.cardActionsChip}
|
||||
onPress={() => console.log('Pressed')}
|
||||
>
|
||||
{t('menuItems.followers', { followers: followersCount })}
|
||||
</Chip>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
)}
|
||||
{publicKey && (
|
||||
<Drawer.Section>
|
||||
<Drawer.Item
|
||||
label={props.label}
|
||||
icon={props.icon}
|
||||
key={props.key}
|
||||
active={drawerItemIndex === index}
|
||||
onPress={() => onPressItem(index)}
|
||||
label={t('menuItems.relays')}
|
||||
icon='message-question-outline'
|
||||
key='relays'
|
||||
active={drawerItemIndex === 0}
|
||||
onPress={() => onPressItem('relays', 0)}
|
||||
onTouchEnd={() => setDrawerItemIndex(-1)}
|
||||
right={props.right}
|
||||
right={() =>
|
||||
relays.length < 1 ? (
|
||||
<Text style={{ color: theme.colors.error }}>{t('menuItems.notConnected')}</Text>
|
||||
) : (
|
||||
<Text style={{ color: theme.colors.inversePrimary }}>
|
||||
{t('menuItems.connectedRelays', { number: relays.length })}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Drawer.Section>
|
||||
{/* <Drawer.Item
|
||||
label={t('menuItems.configuration')}
|
||||
icon='cog-outline'
|
||||
key='config'
|
||||
active={drawerItemIndex === 1}
|
||||
onPress={() => onPressItem('config', 1)}
|
||||
onTouchEnd={() => setDrawerItemIndex(-1)}
|
||||
/> */}
|
||||
</Drawer.Section>
|
||||
)}
|
||||
<Drawer.Section showDivider={false}>
|
||||
{DrawerBottomItemsData.map((props, index) => (
|
||||
<Drawer.Item
|
||||
label={props.label}
|
||||
icon={props.icon}
|
||||
key={props.key}
|
||||
active={drawerItemIndex === DrawerItemsData.length + index}
|
||||
onPress={() => onPressItem(DrawerItemsData.length + index)}
|
||||
onTouchEnd={() => setDrawerItemIndex(-1)}
|
||||
/>
|
||||
))}
|
||||
<Drawer.Item
|
||||
label={t('menuItems.about')}
|
||||
icon='message-question-outline'
|
||||
key='about'
|
||||
active={drawerItemIndex === 2}
|
||||
onPress={() => onPressItem('about', 2)}
|
||||
onTouchEnd={() => setDrawerItemIndex(-1)}
|
||||
/>
|
||||
</Drawer.Section>
|
||||
</DrawerContentScrollView>
|
||||
{publicKey && (
|
||||
@ -142,12 +165,34 @@ export const MenuItems: React.FC<MenuItemsProps> = ({ navigation }) => {
|
||||
const styles = StyleSheet.create({
|
||||
drawerContent: {
|
||||
flex: 1,
|
||||
borderTopRightRadius: 28
|
||||
borderTopRightRadius: 28,
|
||||
},
|
||||
cardContainer: {
|
||||
margin: 12,
|
||||
},
|
||||
cardActions: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
cardActionsChip: {
|
||||
width: '47%',
|
||||
},
|
||||
cardAvatar: {
|
||||
marginRight: 14,
|
||||
},
|
||||
cardContent: {
|
||||
width: '100%',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
cardEdit: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
flex: 1,
|
||||
},
|
||||
bottomSection: {
|
||||
padding: 24,
|
||||
marginBottom: 0,
|
||||
borderBottomRightRadius: 28
|
||||
borderBottomRightRadius: 28,
|
||||
padding: 24,
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -3,10 +3,11 @@ import { BottomNavigation, BottomNavigationTab, useTheme } from '@ui-kitten/comp
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import Icon from 'react-native-vector-icons/FontAwesome5'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { UserContext } from '../../Contexts/UserContext'
|
||||
|
||||
export const NavigationBar: React.FC = () => {
|
||||
const { goToPage, getActualPage, page } = useContext(AppContext)
|
||||
const { publicKey, privateKey } = useContext(RelayPoolContext)
|
||||
const { publicKey, privateKey } = React.useContext(UserContext)
|
||||
const theme = useTheme()
|
||||
const profilePage = `profile#${publicKey ?? ''}`
|
||||
|
||||
|
@ -35,7 +35,8 @@ export const NoteCard: React.FC<NoteCardProps> = ({
|
||||
onlyContactsReplies = false,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const { relayPool, publicKey, privateKey, lastEventId } = useContext(RelayPoolContext)
|
||||
const { publicKey, privateKey } = React.useContext(UserContext)
|
||||
const { relayPool, lastEventId } = useContext(RelayPoolContext)
|
||||
const { database, goToPage } = useContext(AppContext)
|
||||
const [relayAdded, setRelayAdded] = useState<boolean>(false)
|
||||
const [replies, setReplies] = useState<Note[]>([])
|
||||
|
@ -2,13 +2,8 @@ import React, { useEffect, useState } from 'react'
|
||||
import { QuickSQLiteConnection } from 'react-native-quick-sqlite'
|
||||
import { initDatabase } from '../Functions/DatabaseFunctions'
|
||||
import SInfo from 'react-native-sensitive-info'
|
||||
import { BackHandler } from 'react-native'
|
||||
|
||||
export interface AppContextProps {
|
||||
page: string
|
||||
goToPage: (path: string, root?: boolean) => void
|
||||
goBack: () => void
|
||||
getActualPage: () => string
|
||||
init: () => void
|
||||
loadingDb: boolean
|
||||
database: QuickSQLiteConnection | null
|
||||
@ -19,65 +14,29 @@ export interface AppContextProviderProps {
|
||||
}
|
||||
|
||||
export const initialAppContext: AppContextProps = {
|
||||
page: '',
|
||||
init: () => {},
|
||||
goToPage: () => {},
|
||||
getActualPage: () => '',
|
||||
goBack: () => {},
|
||||
loadingDb: true,
|
||||
database: null,
|
||||
}
|
||||
|
||||
export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.Element => {
|
||||
const [page, setPage] = useState<string>(initialAppContext.page)
|
||||
const [database, setDatabase] = useState<QuickSQLiteConnection | null>(null)
|
||||
const [loadingDb, setLoadingDb] = useState<boolean>(initialAppContext.loadingDb)
|
||||
|
||||
const init: () => void = () => {
|
||||
const db = initDatabase()
|
||||
setDatabase(db)
|
||||
SInfo.getItem('privateKey', {}).then(() => {
|
||||
SInfo.getItem('publicKey', {}).then((value) => {
|
||||
setLoadingDb(false)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(init, [])
|
||||
|
||||
useEffect(() => {
|
||||
BackHandler.addEventListener('hardwareBackPress', () => {
|
||||
goBack()
|
||||
return true
|
||||
})
|
||||
}, [page])
|
||||
|
||||
const goToPage: (path: string, root?: boolean) => void = (path, root) => {
|
||||
if (page !== '' && !root) {
|
||||
setPage(`${page}%${path}`)
|
||||
} else {
|
||||
setPage(path)
|
||||
}
|
||||
}
|
||||
|
||||
const goBack: () => void = () => {
|
||||
const breadcrump = page.split('%')
|
||||
if (breadcrump.length > 1) {
|
||||
setPage(breadcrump.slice(0, -1).join('%'))
|
||||
}
|
||||
}
|
||||
|
||||
const getActualPage: () => string = () => {
|
||||
const breadcrump = page.split('%')
|
||||
return breadcrump[breadcrump.length - 1]
|
||||
}
|
||||
|
||||
return (
|
||||
<AppContext.Provider
|
||||
value={{
|
||||
page,
|
||||
init,
|
||||
goToPage,
|
||||
goBack,
|
||||
getActualPage,
|
||||
loadingDb,
|
||||
database,
|
||||
}}
|
||||
|
@ -1,21 +1,20 @@
|
||||
import React, { useContext, useEffect, useMemo, useState } from 'react'
|
||||
import RelayPool from '../lib/nostr/RelayPool/intex'
|
||||
import { AppContext } from './AppContext'
|
||||
import SInfo from 'react-native-sensitive-info'
|
||||
import { getPublickey } from '../lib/nostr/Bip'
|
||||
import { DeviceEventEmitter } from 'react-native'
|
||||
import debounce from 'lodash.debounce'
|
||||
import { getRelays, Relay } from '../Functions/DatabaseFunctions/Relays'
|
||||
import { UserContext } from './UserContext'
|
||||
|
||||
export interface RelayPoolContextProps {
|
||||
loadingRelayPool: boolean
|
||||
relayPool?: RelayPool
|
||||
setRelayPool: (relayPool: RelayPool) => void
|
||||
publicKey?: string
|
||||
setPublicKey: (privateKey: string | undefined) => void
|
||||
privateKey?: string
|
||||
setPrivateKey: (privateKey: string | undefined) => void
|
||||
lastEventId?: string
|
||||
lastConfirmationtId?: string
|
||||
relays: Relay[]
|
||||
addRelayItem: (relay: Relay) => Promise<void>
|
||||
removeRelayItem: (relay: Relay) => Promise<void>
|
||||
}
|
||||
|
||||
export interface WebsocketEvent {
|
||||
@ -29,26 +28,26 @@ export interface RelayPoolContextProviderProps {
|
||||
|
||||
export const initialRelayPoolContext: RelayPoolContextProps = {
|
||||
loadingRelayPool: true,
|
||||
setPublicKey: () => {},
|
||||
setPrivateKey: () => {},
|
||||
setRelayPool: () => {},
|
||||
addRelayItem: async () => await new Promise(() => {}),
|
||||
removeRelayItem: async () => await new Promise(() => {}),
|
||||
relays: []
|
||||
}
|
||||
|
||||
export const RelayPoolContextProvider = ({
|
||||
children,
|
||||
images,
|
||||
}: RelayPoolContextProviderProps): JSX.Element => {
|
||||
const { database, loadingDb, goToPage, page } = useContext(AppContext)
|
||||
const { database } = useContext(AppContext)
|
||||
const { publicKey, privateKey } = React.useContext(UserContext)
|
||||
|
||||
const [publicKey, setPublicKey] = useState<string>()
|
||||
const [privateKey, setPrivateKey] = useState<string>()
|
||||
const [relayPool, setRelayPool] = useState<RelayPool>()
|
||||
const [loadingRelayPool, setLoadingRelayPool] = useState<boolean>(
|
||||
initialRelayPoolContext.loadingRelayPool,
|
||||
)
|
||||
const [lastEventId, setLastEventId] = useState<string>('')
|
||||
const [lastConfirmationtId, setLastConfirmationId] = useState<string>('')
|
||||
const [lastPage, setLastPage] = useState<string>(page)
|
||||
const [relays, setRelays] = React.useState<Relay[]>([])
|
||||
|
||||
const changeEventIdHandler: (event: WebsocketEvent) => void = (event) => {
|
||||
setLastEventId(event.eventId)
|
||||
@ -74,52 +73,44 @@ export const RelayPoolContextProvider = ({
|
||||
initRelayPool.connect(publicKey, (eventId: string) => setLastEventId(eventId))
|
||||
setRelayPool(initRelayPool)
|
||||
setLoadingRelayPool(false)
|
||||
loadRelays()
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (relayPool && lastPage !== page) {
|
||||
setLastPage(page)
|
||||
const loadRelays: () => void = () => {
|
||||
if (database) {
|
||||
getRelays(database).then((results) => setRelays(results))
|
||||
}
|
||||
}, [page])
|
||||
}
|
||||
|
||||
|
||||
const addRelayItem: (relay: Relay) => Promise<void> = async (relay) => {
|
||||
return await new Promise((resolve, _reject) => {
|
||||
if (relayPool && database && publicKey) {
|
||||
relayPool.add(relay.url, () => {
|
||||
setRelays((prev) => [...prev, relay])
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const removeRelayItem: (relay: Relay) => Promise<void> = async (relay) => {
|
||||
return await new Promise((resolve, _reject) => {
|
||||
if (relayPool && database && publicKey) {
|
||||
relayPool.remove(relay.url, () => {
|
||||
setRelays((prev) => prev.filter((item) => item.url !== relay.url))
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (publicKey && publicKey !== '') {
|
||||
SInfo.setItem('publicKey', publicKey, {})
|
||||
if (!loadingRelayPool && page !== 'landing') {
|
||||
goToPage('home', true)
|
||||
} else {
|
||||
loadRelayPool()
|
||||
}
|
||||
loadRelayPool()
|
||||
}
|
||||
}, [publicKey, loadingRelayPool])
|
||||
|
||||
useEffect(() => {
|
||||
if (privateKey && privateKey !== '') {
|
||||
SInfo.setItem('privateKey', privateKey, {})
|
||||
const publicKey: string = getPublickey(privateKey)
|
||||
setPublicKey(publicKey)
|
||||
}
|
||||
}, [privateKey])
|
||||
|
||||
useEffect(() => {
|
||||
if (!loadingDb) {
|
||||
SInfo.getItem('privateKey', {}).then((privateResult) => {
|
||||
if (privateResult && privateResult !== '') {
|
||||
setPrivateKey(privateResult)
|
||||
setPublicKey(getPublickey(privateResult))
|
||||
} else {
|
||||
SInfo.getItem('publicKey', {}).then((publicResult) => {
|
||||
if (publicResult && publicResult !== '') {
|
||||
setPublicKey(publicResult)
|
||||
} else {
|
||||
goToPage('landing', true)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [loadingDb])
|
||||
}, [publicKey])
|
||||
|
||||
return (
|
||||
<RelayPoolContext.Provider
|
||||
@ -127,12 +118,11 @@ export const RelayPoolContextProvider = ({
|
||||
loadingRelayPool,
|
||||
relayPool,
|
||||
setRelayPool,
|
||||
publicKey,
|
||||
setPublicKey,
|
||||
privateKey,
|
||||
setPrivateKey,
|
||||
lastEventId,
|
||||
lastConfirmationtId,
|
||||
relays,
|
||||
addRelayItem,
|
||||
removeRelayItem
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
143
frontend/Contexts/UserContext.tsx
Normal file
143
frontend/Contexts/UserContext.tsx
Normal file
@ -0,0 +1,143 @@
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import { getPublickey } from '../lib/nostr/Bip'
|
||||
import SInfo from 'react-native-sensitive-info'
|
||||
import { RelayPoolContext } from './RelayPoolContext'
|
||||
import { AppContext } from './AppContext'
|
||||
import { getContactsCount, getFollowersCount, getUser, User } from '../Functions/DatabaseFunctions/Users'
|
||||
import { dropTables } from '../Functions/DatabaseFunctions'
|
||||
import { navigate, jumpTo } from '../lib/Navigation'
|
||||
import { npubEncode, nsecEncode } from 'nostr-tools/nip19'
|
||||
|
||||
export interface UserContextProps {
|
||||
nPub?: string
|
||||
nSec?: string
|
||||
publicKey?: string
|
||||
setPublicKey: (privateKey: string | undefined) => void
|
||||
privateKey?: string
|
||||
setPrivateKey: (privateKey: string | undefined) => void
|
||||
setUser: (user: User) => void
|
||||
user?: User,
|
||||
contactsCount: number,
|
||||
followersCount: number,
|
||||
reloadUser: () => void
|
||||
logout: () => void
|
||||
}
|
||||
|
||||
export interface UserContextProviderProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export const initialUserContext: UserContextProps = {
|
||||
setPublicKey: () => {},
|
||||
setPrivateKey: () => {},
|
||||
setUser: () => {},
|
||||
reloadUser: () => {},
|
||||
logout: () => {},
|
||||
contactsCount: 0,
|
||||
followersCount: 0
|
||||
}
|
||||
|
||||
export const UserContextProvider = ({ children }: UserContextProviderProps): JSX.Element => {
|
||||
const { database, loadingDb, init } = useContext(AppContext)
|
||||
const { relayPool } = React.useContext(RelayPoolContext)
|
||||
const [publicKey, setPublicKey] = useState<string>()
|
||||
const [nPub, setNpub] = useState<string>()
|
||||
const [nSec, setNsec] = useState<string>()
|
||||
const [privateKey, setPrivateKey] = useState<string>()
|
||||
const [user, setUser] = React.useState<User>()
|
||||
const [contactsCount, setContantsCount] = React.useState<number>(0)
|
||||
const [followersCount, setFollowersCount] = React.useState<number>(0)
|
||||
|
||||
const reloadUser: () => void = () => {
|
||||
if (database && publicKey) {
|
||||
getUser(publicKey, database).then((result) => {
|
||||
if (result) setUser(result)
|
||||
})
|
||||
getContactsCount(database).then(setContantsCount)
|
||||
getFollowersCount(database).then(setFollowersCount)
|
||||
}
|
||||
}
|
||||
|
||||
const logout: () => void = () => {
|
||||
if (database) {
|
||||
relayPool?.unsubscribeAll()
|
||||
setPrivateKey(undefined)
|
||||
setPublicKey(undefined)
|
||||
setNpub(undefined)
|
||||
setNsec(undefined)
|
||||
setUser(undefined)
|
||||
dropTables(database).then(() => {
|
||||
SInfo.deleteItem('privateKey', {}).then(() => {
|
||||
SInfo.deleteItem('publicKey', {}).then(() => {
|
||||
init()
|
||||
navigate('Home', { screen: 'ProfileConnect' })
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (privateKey && privateKey !== '') {
|
||||
SInfo.setItem('privateKey', privateKey, {})
|
||||
setNsec(nsecEncode(privateKey))
|
||||
const publicKey: string = getPublickey(privateKey)
|
||||
setPublicKey(publicKey)
|
||||
}
|
||||
}, [privateKey])
|
||||
|
||||
useEffect(() => {
|
||||
if (publicKey && publicKey !== '') {
|
||||
SInfo.setItem('publicKey', publicKey, {})
|
||||
setNpub(npubEncode(publicKey))
|
||||
reloadUser()
|
||||
}
|
||||
}, [publicKey])
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
navigate('Feed')
|
||||
}
|
||||
}, [user])
|
||||
|
||||
useEffect(() => {
|
||||
if (!loadingDb ) {
|
||||
SInfo.getItem('privateKey', {}).then((privateResult) => {
|
||||
if (privateResult && privateResult !== '') {
|
||||
setPrivateKey(privateResult)
|
||||
setPublicKey(getPublickey(privateResult))
|
||||
} else {
|
||||
SInfo.getItem('publicKey', {}).then((publicResult) => {
|
||||
if (publicResult && publicResult !== '') {
|
||||
setPublicKey(publicResult)
|
||||
jumpTo('Feed')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [loadingDb])
|
||||
|
||||
return (
|
||||
<UserContext.Provider
|
||||
value={{
|
||||
nSec,
|
||||
nPub,
|
||||
setUser,
|
||||
publicKey,
|
||||
setPublicKey,
|
||||
privateKey,
|
||||
setPrivateKey,
|
||||
user,
|
||||
contactsCount,
|
||||
followersCount,
|
||||
reloadUser,
|
||||
logout
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</UserContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const UserContext = React.createContext(initialUserContext)
|
@ -37,7 +37,7 @@ export const getReactionsCount: (
|
||||
const resultSet = await db.execute(notesQuery)
|
||||
const item: { 'COUNT(*)': number } = resultSet?.rows?.item(0)
|
||||
|
||||
return item['COUNT(*)']
|
||||
return item['COUNT(*)'] ?? 0
|
||||
}
|
||||
|
||||
export const getUserReaction: (
|
||||
|
@ -28,3 +28,4 @@ export const getRelays: (db: QuickSQLiteConnection) => Promise<Relay[]> = async
|
||||
const relays: Relay[] = items.map((object) => databaseToEntity(object))
|
||||
return relays
|
||||
}
|
||||
|
||||
|
@ -54,6 +54,22 @@ export const addUser: (pubKey: string, db: QuickSQLiteConnection) => Promise<Que
|
||||
return db.execute(query, [pubKey])
|
||||
}
|
||||
|
||||
export const getContactsCount: (db: QuickSQLiteConnection) => Promise<number> = async (db) => {
|
||||
const countQuery = 'SELECT COUNT(*) FROM nostros_users WHERE contact = 1'
|
||||
const resultSet = db.execute(countQuery)
|
||||
const item: { 'COUNT(*)': number } = resultSet?.rows?.item(0)
|
||||
|
||||
return item['COUNT(*)'] ?? 0
|
||||
}
|
||||
|
||||
export const getFollowersCount: (db: QuickSQLiteConnection) => Promise<number> = async (db) => {
|
||||
const countQuery = 'SELECT COUNT(*) FROM nostros_users WHERE follower = 1'
|
||||
const resultSet = db.execute(countQuery)
|
||||
const item: { 'COUNT(*)': number } = resultSet?.rows?.item(0)
|
||||
|
||||
return item['COUNT(*)'] ?? 0
|
||||
}
|
||||
|
||||
export const getUsers: (
|
||||
db: QuickSQLiteConnection,
|
||||
options: {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"common": {
|
||||
"loggerPage": {
|
||||
"homeNavigator": {
|
||||
"ProfileConnect": "",
|
||||
"ProfileLoad": ""
|
||||
},
|
||||
@ -10,7 +10,12 @@
|
||||
"menuItems": {
|
||||
"relays": "Relays",
|
||||
"notConnected": "Not connected",
|
||||
"connectedRelays": "{{number}} connected"
|
||||
"connectedRelays": "{{number}} connected",
|
||||
"following": "{{following}} following",
|
||||
"followers": "{{followers}} followers",
|
||||
"configuration": "Configuration",
|
||||
"about": "About",
|
||||
"logout": "Logout"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,244 +0,0 @@
|
||||
import { Divider, Input, Layout, TopNavigation, useTheme } from '@ui-kitten/components'
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import { Clipboard, ScrollView, StyleSheet } from 'react-native'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import Icon from 'react-native-vector-icons/FontAwesome5'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { dropTables } from '../../Functions/DatabaseFunctions'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import SInfo from 'react-native-sensitive-info'
|
||||
import { getUser } from '../../Functions/DatabaseFunctions/Users'
|
||||
import { EventKind } from '../../lib/nostr/Events'
|
||||
import moment from 'moment'
|
||||
import { showMessage } from 'react-native-flash-message'
|
||||
import { Button } from '../../Components'
|
||||
|
||||
export const ConfigPage: React.FC = () => {
|
||||
const theme = useTheme()
|
||||
const { goToPage, goBack, database, init } = useContext(AppContext)
|
||||
const { setPrivateKey, setPublicKey, relayPool, publicKey, privateKey } =
|
||||
useContext(RelayPoolContext)
|
||||
// State
|
||||
const [name, setName] = useState<string>()
|
||||
const [picture, setPicture] = useState<string>()
|
||||
const [about, setAbout] = useState<string>()
|
||||
const [lnurl, setLnurl] = useState<string>()
|
||||
const [isPublishingProfile, setIsPublishingProfile] = useState<boolean>(false)
|
||||
const [nip05, setNip05] = useState<string>()
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
useEffect(() => {
|
||||
relayPool?.unsubscribeAll()
|
||||
if (database && publicKey) {
|
||||
getUser(publicKey, database).then((user) => {
|
||||
if (user) {
|
||||
setName(user.name)
|
||||
setPicture(user.picture)
|
||||
setAbout(user.about)
|
||||
setLnurl(user.lnurl)
|
||||
setNip05(user.nip05)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
const onPressBack: () => void = () => {
|
||||
relayPool?.unsubscribeAll()
|
||||
goBack()
|
||||
}
|
||||
|
||||
const onPressLogout: () => void = () => {
|
||||
if (database) {
|
||||
relayPool?.unsubscribeAll()
|
||||
setPrivateKey(undefined)
|
||||
setPublicKey(undefined)
|
||||
dropTables(database).then(() => {
|
||||
SInfo.deleteItem('privateKey', {}).then(() => {
|
||||
SInfo.deleteItem('publicKey', {}).then(() => {
|
||||
init()
|
||||
goToPage('landing', true)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const onPushPublishProfile: () => void = () => {
|
||||
if (publicKey) {
|
||||
setIsPublishingProfile(true)
|
||||
relayPool
|
||||
?.sendEvent({
|
||||
content: JSON.stringify({
|
||||
name,
|
||||
about,
|
||||
picture,
|
||||
lud06: lnurl,
|
||||
nip05,
|
||||
}),
|
||||
created_at: moment().unix(),
|
||||
kind: EventKind.meta,
|
||||
pubkey: publicKey,
|
||||
tags: [],
|
||||
})
|
||||
.then(() => {
|
||||
showMessage({
|
||||
message: t('alerts.profilePublished'),
|
||||
duration: 4000,
|
||||
type: 'success',
|
||||
})
|
||||
setIsPublishingProfile(false) // restore sending status
|
||||
})
|
||||
.catch((err) => {
|
||||
showMessage({
|
||||
message: t('alerts.profilePublishError'),
|
||||
description: err.message,
|
||||
type: 'danger',
|
||||
})
|
||||
setIsPublishingProfile(false) // restore sending status
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const renderBackAction = (): JSX.Element => (
|
||||
<Button
|
||||
accessoryRight={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
||||
onPress={onPressBack}
|
||||
appearance='ghost'
|
||||
/>
|
||||
)
|
||||
|
||||
const copyToClipboard: (value: string) => JSX.Element = (value) => {
|
||||
const copy: () => void = () => Clipboard.setString(value)
|
||||
|
||||
return <Icon name={'copy'} size={16} color={theme['text-basic-color']} solid onPress={copy} />
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
actionContainer: {
|
||||
marginTop: 30,
|
||||
paddingLeft: 32,
|
||||
paddingRight: 32,
|
||||
paddingBottom: 32,
|
||||
},
|
||||
action: {
|
||||
backgroundColor: 'transparent',
|
||||
marginTop: 30,
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Layout style={styles.container} level='2'>
|
||||
<TopNavigation
|
||||
alignment='center'
|
||||
title={t('configPage.title')}
|
||||
accessoryLeft={renderBackAction}
|
||||
/>
|
||||
<ScrollView horizontal={false}>
|
||||
<Layout style={styles.actionContainer} level='2'>
|
||||
<Layout style={styles.action}>
|
||||
<Button
|
||||
onPress={() => goToPage('relays')}
|
||||
status='warning'
|
||||
accessoryLeft={
|
||||
<Icon name='server' size={16} color={theme['text-basic-color']} solid />
|
||||
}
|
||||
>
|
||||
{t('configPage.relays')}
|
||||
</Button>
|
||||
</Layout>
|
||||
<Layout style={styles.action}>
|
||||
<Divider />
|
||||
</Layout>
|
||||
<Layout style={styles.action}>
|
||||
<Input
|
||||
placeholder={t('configPage.username')}
|
||||
value={name}
|
||||
onChangeText={setName}
|
||||
label={t('configPage.username')}
|
||||
/>
|
||||
</Layout>
|
||||
<Layout style={styles.action}>
|
||||
<Input
|
||||
placeholder={t('configPage.picture')}
|
||||
value={picture}
|
||||
onChangeText={setPicture}
|
||||
label={t('configPage.picture')}
|
||||
/>
|
||||
</Layout>
|
||||
<Layout style={styles.action}>
|
||||
<Input
|
||||
placeholder={t('configPage.lnurl')}
|
||||
value={lnurl}
|
||||
onChangeText={setLnurl}
|
||||
label={t('configPage.lnurl')}
|
||||
/>
|
||||
</Layout>
|
||||
<Layout style={styles.action}>
|
||||
<Input
|
||||
placeholder={t('configPage.nip05')}
|
||||
value={nip05}
|
||||
onChangeText={setNip05}
|
||||
label={t('configPage.nip05')}
|
||||
/>
|
||||
</Layout>
|
||||
<Layout style={styles.action}>
|
||||
<Input
|
||||
placeholder={t('configPage.about')}
|
||||
multiline={true}
|
||||
textStyle={{ minHeight: 64 }}
|
||||
value={about}
|
||||
onChangeText={setAbout}
|
||||
label={t('configPage.about')}
|
||||
/>
|
||||
</Layout>
|
||||
<Layout style={styles.action}>
|
||||
<Button
|
||||
onPress={onPushPublishProfile}
|
||||
status='success'
|
||||
loading={isPublishingProfile}
|
||||
accessoryLeft={
|
||||
<Icon name='paper-plane' size={16} color={theme['text-basic-color']} solid />
|
||||
}
|
||||
>
|
||||
{t('configPage.publish')}
|
||||
</Button>
|
||||
</Layout>
|
||||
<Layout style={styles.action}>
|
||||
<Divider />
|
||||
</Layout>
|
||||
<Layout style={styles.action}>
|
||||
<Input
|
||||
disabled={true}
|
||||
placeholder={t('configPage.publicKey')}
|
||||
accessoryRight={() => copyToClipboard(publicKey ?? '')}
|
||||
value={publicKey}
|
||||
label={t('configPage.publicKey')}
|
||||
/>
|
||||
</Layout>
|
||||
<Layout style={styles.action}>
|
||||
<Input
|
||||
disabled={true}
|
||||
placeholder={t('configPage.privateKey')}
|
||||
accessoryRight={() => copyToClipboard(privateKey ?? '')}
|
||||
value={privateKey}
|
||||
secureTextEntry={true}
|
||||
label={t('configPage.privateKey')}
|
||||
/>
|
||||
</Layout>
|
||||
<Layout style={styles.action}>
|
||||
<Button onPress={onPressLogout} status='danger'>
|
||||
{t('configPage.logout')}
|
||||
</Button>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</ScrollView>
|
||||
</Layout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfigPage
|
@ -20,10 +20,12 @@ import { Button, UserCard } from '../../Components'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { populatePets } from '../../Functions/RelayFunctions/Users'
|
||||
import { getNip19Key } from '../../lib/nostr/Nip19'
|
||||
import { UserContext } from '../../Contexts/UserContext'
|
||||
|
||||
export const ContactsPage: React.FC = () => {
|
||||
const { database, goBack } = useContext(AppContext)
|
||||
const { relayPool, publicKey, privateKey, lastEventId } = useContext(RelayPoolContext)
|
||||
const { database } = useContext(AppContext)
|
||||
const { publicKey, privateKey } = React.useContext(UserContext)
|
||||
const { relayPool, lastEventId } = useContext(RelayPoolContext)
|
||||
const theme = useTheme()
|
||||
// State
|
||||
const [users, setUsers] = useState<User[]>()
|
||||
|
@ -6,6 +6,10 @@ import { Appbar, Snackbar, Text, useTheme } from 'react-native-paper'
|
||||
import RBSheet from "react-native-raw-bottom-sheet"
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import HomePage from '../HomePage'
|
||||
import RelaysPage from '../RelaysPage'
|
||||
import AboutPage from '../AboutPage'
|
||||
import ProfileConfigPage from '../ProfileConfigPage'
|
||||
import ProfilePage from '../ProfilePage'
|
||||
|
||||
export const HomeNavigator: React.FC = () => {
|
||||
const theme = useTheme()
|
||||
@ -20,10 +24,6 @@ export const HomeNavigator: React.FC = () => {
|
||||
[],
|
||||
)
|
||||
|
||||
const onPressQuestion: () => void = () => {
|
||||
bottomSheetRef.current?.open()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Navigator
|
||||
@ -43,8 +43,7 @@ export const HomeNavigator: React.FC = () => {
|
||||
onPress={() => (navigation as any as DrawerNavigationProp<{}>).openDrawer()}
|
||||
/>
|
||||
) : null}
|
||||
<Appbar.Content title={t(`loggerPage.${route.name}`)} />
|
||||
<Appbar.Action icon='help-circle-outline' isLeading onPress={onPressQuestion} />
|
||||
<Appbar.Content title={t(`homeNavigator.${route.name}`)} />
|
||||
</Appbar.Header>
|
||||
)
|
||||
},
|
||||
@ -52,7 +51,13 @@ export const HomeNavigator: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
<Stack.Group>
|
||||
<Stack.Screen name='Feed' component={HomePage} />
|
||||
<Stack.Screen name='Landing' component={HomePage} />
|
||||
</Stack.Group>
|
||||
<Stack.Group>
|
||||
<Stack.Screen name='Relays' component={RelaysPage} />
|
||||
<Stack.Screen name='About' component={AboutPage} />
|
||||
<Stack.Screen name='ProfileConfig' component={ProfileConfigPage} />
|
||||
<Stack.Screen name='Profile' component={ProfilePage} />
|
||||
</Stack.Group>
|
||||
</Stack.Navigator>
|
||||
<RBSheet
|
||||
|
@ -107,7 +107,7 @@ export const HomeNavigator: React.FC = () => {
|
||||
return (
|
||||
<Appbar.Header>
|
||||
{leftAction()}
|
||||
<Appbar.Content title={t(`loggerPage.${route.name}`)} />
|
||||
<Appbar.Content title={t(`homeNavigator.${route.name}`)} />
|
||||
<Appbar.Action icon='help-circle-outline' isLeading onPress={() => onPressQuestion(route.name)} />
|
||||
</Appbar.Header>
|
||||
)
|
||||
|
@ -11,13 +11,13 @@ import {
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import { getMainNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
|
||||
import NoteCard from '../../Components/NoteCard'
|
||||
import Icon from 'react-native-vector-icons/FontAwesome5'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { EventKind } from '../../lib/nostr/Events'
|
||||
import { getReplyEventId } from '../../Functions/RelayFunctions/Events'
|
||||
import { getUsers, User } from '../../Functions/DatabaseFunctions/Users'
|
||||
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
|
||||
import { RelayFilters } from '../../lib/nostr/RelayPool/intex'
|
||||
import { useTheme } from 'react-native-paper'
|
||||
|
||||
export const HomePage: React.FC = () => {
|
||||
const { database, goToPage } = useContext(AppContext)
|
||||
|
537
frontend/Pages/ProfileConfigPage/index.tsx
Normal file
537
frontend/Pages/ProfileConfigPage/index.tsx
Normal file
@ -0,0 +1,537 @@
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import { Clipboard, Linking, ScrollView, StyleSheet, View } from 'react-native'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { UserContext } from '../../Contexts/UserContext'
|
||||
import { getUser } from '../../Functions/DatabaseFunctions/Users'
|
||||
import { EventKind } from '../../lib/nostr/Events'
|
||||
import moment from 'moment'
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
Card,
|
||||
useTheme,
|
||||
IconButton,
|
||||
Text,
|
||||
TouchableRipple,
|
||||
TextInput,
|
||||
Snackbar,
|
||||
} from 'react-native-paper'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import RBSheet from 'react-native-raw-bottom-sheet'
|
||||
|
||||
export const ProfileConfigPage: React.FC = () => {
|
||||
const theme = useTheme()
|
||||
const bottomSheetPictureRef = React.useRef<RBSheet>(null)
|
||||
const bottomSheetDirectoryRef = React.useRef<RBSheet>(null)
|
||||
const bottomSheetNip05Ref = React.useRef<RBSheet>(null)
|
||||
const bottomSheetLud06Ref = React.useRef<RBSheet>(null)
|
||||
const { database } = useContext(AppContext)
|
||||
const { relayPool } = useContext(RelayPoolContext)
|
||||
const { user, publicKey, nPub, nSec, contactsCount, followersCount, setUser } =
|
||||
useContext(UserContext)
|
||||
// State
|
||||
const [name, setName] = useState<string>()
|
||||
const [picture, setPicture] = useState<string>()
|
||||
const [about, setAbout] = useState<string>()
|
||||
const [lnurl, setLnurl] = useState<string>()
|
||||
const [isPublishingProfile, setIsPublishingProfile] = useState<boolean>(false)
|
||||
const [nip05, setNip05] = useState<string>()
|
||||
const [showNotification, setShowNotification] = useState<
|
||||
| 'npubCopied'
|
||||
| 'picturePublished'
|
||||
| 'connectionError'
|
||||
| 'nsecCopied'
|
||||
| 'profilePublished'
|
||||
| 'nip05Published'
|
||||
| 'lud06Published'
|
||||
>()
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
useEffect(() => {
|
||||
relayPool?.unsubscribeAll()
|
||||
if (database && publicKey) {
|
||||
if (user) {
|
||||
setName(user.name)
|
||||
setPicture(user.picture)
|
||||
setAbout(user.about)
|
||||
setLnurl(user.lnurl)
|
||||
setNip05(user.nip05)
|
||||
}
|
||||
}
|
||||
}, [user])
|
||||
|
||||
const onPressSavePicture: () => void = () => {
|
||||
if (publicKey && database) {
|
||||
getUser(publicKey, database).then((user) => {
|
||||
if (user) {
|
||||
relayPool
|
||||
?.sendEvent({
|
||||
content: JSON.stringify({
|
||||
name: user.name,
|
||||
about: user.about,
|
||||
picture,
|
||||
lud06: user.lnurl,
|
||||
nip05: user.nip05,
|
||||
}),
|
||||
created_at: moment().unix(),
|
||||
kind: EventKind.meta,
|
||||
pubkey: publicKey,
|
||||
tags: [],
|
||||
})
|
||||
.then(() => {
|
||||
setIsPublishingProfile(false) // restore sending status
|
||||
setShowNotification('picturePublished')
|
||||
setUser({
|
||||
...user,
|
||||
picture,
|
||||
})
|
||||
bottomSheetPictureRef.current?.close()
|
||||
})
|
||||
.catch(() => {
|
||||
setIsPublishingProfile(false) // restore sending status
|
||||
setShowNotification('connectionError')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const onPressSaveNip05: () => void = () => {
|
||||
if (publicKey && database) {
|
||||
getUser(publicKey, database).then((user) => {
|
||||
if (user) {
|
||||
relayPool
|
||||
?.sendEvent({
|
||||
content: JSON.stringify({
|
||||
name: user.name,
|
||||
about: user.about,
|
||||
picture: user.picture,
|
||||
lud06: user.lnurl,
|
||||
nip05,
|
||||
}),
|
||||
created_at: moment().unix(),
|
||||
kind: EventKind.meta,
|
||||
pubkey: publicKey,
|
||||
tags: [],
|
||||
})
|
||||
.then(() => {
|
||||
setIsPublishingProfile(false) // restore sending status
|
||||
setShowNotification('nip05Published')
|
||||
setUser({
|
||||
...user,
|
||||
nip05,
|
||||
})
|
||||
bottomSheetNip05Ref.current?.close()
|
||||
})
|
||||
.catch(() => {
|
||||
setIsPublishingProfile(false) // restore sending status
|
||||
setShowNotification('connectionError')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const onPressSaveLnurl: () => void = () => {
|
||||
if (publicKey && database) {
|
||||
getUser(publicKey, database).then((user) => {
|
||||
if (user) {
|
||||
relayPool
|
||||
?.sendEvent({
|
||||
content: JSON.stringify({
|
||||
name: user.name,
|
||||
about: user.about,
|
||||
picture: user.picture,
|
||||
lnurl,
|
||||
nip05: user.nip05,
|
||||
}),
|
||||
created_at: moment().unix(),
|
||||
kind: EventKind.meta,
|
||||
pubkey: publicKey,
|
||||
tags: [],
|
||||
})
|
||||
.then(() => {
|
||||
setIsPublishingProfile(false) // restore sending status
|
||||
setShowNotification('lud06Published')
|
||||
setUser({
|
||||
...user,
|
||||
lnurl,
|
||||
})
|
||||
bottomSheetLud06Ref.current?.close()
|
||||
})
|
||||
.catch(() => {
|
||||
setIsPublishingProfile(false) // restore sending status
|
||||
setShowNotification('connectionError')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const onPressSaveProfile: () => void = () => {
|
||||
if (publicKey && database) {
|
||||
getUser(publicKey, database).then((user) => {
|
||||
if (user) {
|
||||
relayPool
|
||||
?.sendEvent({
|
||||
content: JSON.stringify({
|
||||
name,
|
||||
about,
|
||||
picture: user.picture,
|
||||
lud06: lnurl,
|
||||
nip05: user.nip05,
|
||||
}),
|
||||
created_at: moment().unix(),
|
||||
kind: EventKind.meta,
|
||||
pubkey: publicKey,
|
||||
tags: [],
|
||||
})
|
||||
.then(() => {
|
||||
setIsPublishingProfile(false) // restore sending status
|
||||
setShowNotification('profilePublished')
|
||||
bottomSheetPictureRef.current?.close()
|
||||
})
|
||||
.catch(() => {
|
||||
setIsPublishingProfile(false) // restore sending status
|
||||
setShowNotification('connectionError')
|
||||
})
|
||||
setUser({
|
||||
...user,
|
||||
name,
|
||||
about,
|
||||
picture,
|
||||
lnurl,
|
||||
nip05,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const rbSheetCustomStyles = React.useMemo(() => {
|
||||
return {
|
||||
container: {
|
||||
...styles.rbsheetContainer,
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
draggableIcon: styles.rbsheetDraggableIcon,
|
||||
}
|
||||
}, [])
|
||||
|
||||
const pastePicture: () => void = () => {
|
||||
Clipboard.getString().then((value) => {
|
||||
setPicture(value ?? '')
|
||||
})
|
||||
}
|
||||
|
||||
const pasteNip05: () => void = () => {
|
||||
Clipboard.getString().then((value) => {
|
||||
setNip05(value ?? '')
|
||||
})
|
||||
}
|
||||
|
||||
const pasteLud06: () => void = () => {
|
||||
Clipboard.getString().then((value) => {
|
||||
setLnurl(value ?? '')
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<ScrollView horizontal={false}>
|
||||
<Card style={styles.cardContainer}>
|
||||
<Card.Content>
|
||||
<View style={styles.cardPicture}>
|
||||
<TouchableRipple onPress={() => bottomSheetPictureRef.current?.open()}>
|
||||
{user?.picture ? (
|
||||
<Avatar.Image size={100} source={{ uri: user.picture }} />
|
||||
) : (
|
||||
<Avatar.Icon
|
||||
size={100}
|
||||
icon='image-plus'
|
||||
style={{ backgroundColor: theme.colors.primaryContainer }}
|
||||
/>
|
||||
)}
|
||||
</TouchableRipple>
|
||||
</View>
|
||||
<View style={styles.cardActions}>
|
||||
<Button mode='elevated'>
|
||||
{t('menuItems.following', { following: contactsCount })}
|
||||
</Button>
|
||||
<Button mode='elevated'>
|
||||
{t('menuItems.followers', { followers: followersCount })}
|
||||
</Button>
|
||||
</View>
|
||||
<View style={styles.cardActions}>
|
||||
<View style={styles.actionButton}>
|
||||
<IconButton
|
||||
icon='content-copy'
|
||||
size={28}
|
||||
onPress={() => {
|
||||
setShowNotification('picturePublished')
|
||||
Clipboard.setString(nPub ?? '')
|
||||
}}
|
||||
/>
|
||||
<Text>{t('profileConfigPage.copyNPub')}</Text>
|
||||
</View>
|
||||
<View style={styles.actionButton}>
|
||||
<IconButton
|
||||
icon='twitter'
|
||||
size={28}
|
||||
onPress={() => bottomSheetDirectoryRef.current?.open()}
|
||||
/>
|
||||
<Text>{t('profileConfigPage.directory')}</Text>
|
||||
</View>
|
||||
<View style={styles.actionButton}>
|
||||
<IconButton
|
||||
icon='lightning-bolt'
|
||||
size={28}
|
||||
iconColor='#F5D112'
|
||||
onPress={() => bottomSheetLud06Ref.current?.open()}
|
||||
/>
|
||||
<Text>{t('profileConfigPage.invoice')}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.cardActions}>
|
||||
<View style={styles.actionButton}>
|
||||
<IconButton
|
||||
icon='check-circle-outline'
|
||||
size={28}
|
||||
onPress={() => bottomSheetNip05Ref.current?.open()}
|
||||
/>
|
||||
<Text>{t('profileConfigPage.nip05')}</Text>
|
||||
</View>
|
||||
<View style={styles.actionButton}></View>
|
||||
<View style={styles.actionButton}></View>
|
||||
</View>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
<TextInput
|
||||
mode='outlined'
|
||||
label={t('profileConfigPage.name') ?? ''}
|
||||
onChangeText={setName}
|
||||
value={name}
|
||||
/>
|
||||
<TextInput
|
||||
mode='outlined'
|
||||
label={t('profileConfigPage.about') ?? ''}
|
||||
onChangeText={setAbout}
|
||||
value={about}
|
||||
/>
|
||||
<TextInput
|
||||
mode='outlined'
|
||||
label={t('profileConfigPage.lud06') ?? ''}
|
||||
onChangeText={setLnurl}
|
||||
value={lnurl}
|
||||
/>
|
||||
<TextInput
|
||||
mode='outlined'
|
||||
label={t('profileConfigPage.npub') ?? ''}
|
||||
value={nPub}
|
||||
right={
|
||||
<TextInput.Icon
|
||||
icon='content-paste'
|
||||
onPress={() => {
|
||||
setShowNotification('npubCopied')
|
||||
Clipboard.setString(nPub ?? '')
|
||||
}}
|
||||
forceTextInputFocus={false}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<TextInput
|
||||
mode='outlined'
|
||||
label={t('profileConfigPage.nsec') ?? ''}
|
||||
value={nSec}
|
||||
secureTextEntry={true}
|
||||
right={
|
||||
<TextInput.Icon
|
||||
icon='content-paste'
|
||||
onPress={() => {
|
||||
setShowNotification('nsecCopied')
|
||||
Clipboard.setString(nSec ?? '')
|
||||
}}
|
||||
forceTextInputFocus={false}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
mode='contained'
|
||||
disabled={!picture || picture === ''}
|
||||
onPress={onPressSaveProfile}
|
||||
loading={isPublishingProfile}
|
||||
>
|
||||
{t('profileConfigPage.publish')}
|
||||
</Button>
|
||||
</ScrollView>
|
||||
<RBSheet
|
||||
ref={bottomSheetPictureRef}
|
||||
closeOnDragDown={true}
|
||||
height={230}
|
||||
customStyles={rbSheetCustomStyles}
|
||||
>
|
||||
<View>
|
||||
<Text variant='titleLarge'>{t('profileConfigPage.pictureTitle')}</Text>
|
||||
<Text variant='bodyMedium'>{t('profileConfigPage.pictureDescription')}</Text>
|
||||
<TextInput
|
||||
mode='outlined'
|
||||
label={t('profileConfigPage.pictureUrl') ?? ''}
|
||||
onChangeText={setPicture}
|
||||
value={picture}
|
||||
right={
|
||||
<TextInput.Icon
|
||||
icon='content-paste'
|
||||
onPress={pastePicture}
|
||||
forceTextInputFocus={false}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
mode='contained'
|
||||
disabled={!picture || picture === ''}
|
||||
onPress={onPressSavePicture}
|
||||
loading={isPublishingProfile}
|
||||
>
|
||||
{t('profileConfigPage.publishPicture')}
|
||||
</Button>
|
||||
</View>
|
||||
</RBSheet>
|
||||
<RBSheet
|
||||
ref={bottomSheetDirectoryRef}
|
||||
closeOnDragDown={true}
|
||||
height={230}
|
||||
customStyles={rbSheetCustomStyles}
|
||||
>
|
||||
<View>
|
||||
<Text variant='titleLarge'>{t('profileConfigPage.directoryTitle')}</Text>
|
||||
<Text variant='bodyMedium'>{t('profileConfigPage.directoryDescription')}</Text>
|
||||
<Button
|
||||
mode='contained'
|
||||
onPress={async () => await Linking.openURL('https://www.nostr.directory')}
|
||||
loading={isPublishingProfile}
|
||||
>
|
||||
{t('profileConfigPage.continue')}
|
||||
</Button>
|
||||
<Button mode='outlined' onPress={() => bottomSheetDirectoryRef.current?.close()}>
|
||||
{t('profileConfigPage.cancell')}
|
||||
</Button>
|
||||
</View>
|
||||
</RBSheet>
|
||||
<RBSheet
|
||||
ref={bottomSheetNip05Ref}
|
||||
closeOnDragDown={true}
|
||||
height={230}
|
||||
customStyles={rbSheetCustomStyles}
|
||||
>
|
||||
<View>
|
||||
<Text variant='titleLarge'>{t('profileConfigPage.pictureTitle')}</Text>
|
||||
<Text variant='bodyMedium'>{t('profileConfigPage.pictureDescription')}</Text>
|
||||
<TextInput
|
||||
mode='outlined'
|
||||
label={t('profileConfigPage.nip05') ?? ''}
|
||||
onChangeText={setNip05}
|
||||
value={nip05}
|
||||
right={
|
||||
<TextInput.Icon
|
||||
icon='content-paste'
|
||||
onPress={pasteNip05}
|
||||
forceTextInputFocus={false}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
mode='contained'
|
||||
disabled={!nip05 || nip05 === ''}
|
||||
onPress={onPressSaveNip05}
|
||||
loading={isPublishingProfile}
|
||||
>
|
||||
{t('profileConfigPage.publishPicture')}
|
||||
</Button>
|
||||
</View>
|
||||
</RBSheet>
|
||||
<RBSheet
|
||||
ref={bottomSheetLud06Ref}
|
||||
closeOnDragDown={true}
|
||||
height={230}
|
||||
customStyles={rbSheetCustomStyles}
|
||||
>
|
||||
<View>
|
||||
<Text variant='titleLarge'>{t('profileConfigPage.lud06Title')}</Text>
|
||||
<Text variant='bodyMedium'>{t('profileConfigPage.lud06Description')}</Text>
|
||||
<TextInput
|
||||
mode='outlined'
|
||||
label={t('profileConfigPage.lud06') ?? ''}
|
||||
onChangeText={setLnurl}
|
||||
value={lnurl}
|
||||
right={
|
||||
<TextInput.Icon
|
||||
icon='content-paste'
|
||||
onPress={pasteLud06}
|
||||
forceTextInputFocus={false}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
mode='contained'
|
||||
disabled={!lnurl || lnurl === ''}
|
||||
onPress={onPressSaveLnurl}
|
||||
loading={isPublishingProfile}
|
||||
>
|
||||
{t('profileConfigPage.publishPicture')}
|
||||
</Button>
|
||||
</View>
|
||||
</RBSheet>
|
||||
<Snackbar
|
||||
style={styles.snackbar}
|
||||
visible={showNotification !== undefined}
|
||||
duration={Snackbar.DURATION_SHORT}
|
||||
onIconPress={() => setShowNotification(undefined)}
|
||||
onDismiss={() => setShowNotification(undefined)}
|
||||
>
|
||||
{t(`profileConfigPage.${showNotification}`)}
|
||||
</Snackbar>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
padding: 16,
|
||||
},
|
||||
cardContainer: {
|
||||
width: '100%',
|
||||
justifyContent: 'center',
|
||||
alignContent: 'center',
|
||||
},
|
||||
cardActions: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around',
|
||||
},
|
||||
cardPicture: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignContent: 'center',
|
||||
marginBottom: 32,
|
||||
},
|
||||
actionButton: {
|
||||
marginTop: 32,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: 80,
|
||||
},
|
||||
rbsheetDraggableIcon: {
|
||||
backgroundColor: '#000',
|
||||
},
|
||||
rbsheetContainer: {
|
||||
padding: 16,
|
||||
borderTopRightRadius: 28,
|
||||
borderTopLeftRadius: 28,
|
||||
},
|
||||
snackbar: {
|
||||
margin: 16,
|
||||
bottom: 70,
|
||||
},
|
||||
})
|
||||
|
||||
export default ProfileConfigPage
|
@ -1,28 +1,20 @@
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import { Clipboard, StyleSheet, View } from 'react-native'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { UserContext } from '../../Contexts/UserContext'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getNip19Key, isPrivateKey, isPublicKey } from '../../lib/nostr/Nip19'
|
||||
import { Button, Switch, Text, TextInput } from 'react-native-paper'
|
||||
import Logo from '../../Components/Logo'
|
||||
import { DrawerNavigationHelpers } from '@react-navigation/drawer/lib/typescript/src/types'
|
||||
import { navigate } from '../../lib/Navigation'
|
||||
|
||||
interface ProfileConnectPageProps {
|
||||
navigation: DrawerNavigationHelpers;
|
||||
}
|
||||
|
||||
export const ProfileConnectPage: React.FC<ProfileConnectPageProps> = ({navigation}) => {
|
||||
const { setPrivateKey, setPublicKey } = useContext(RelayPoolContext)
|
||||
export const ProfileConnectPage: React.FC = () => {
|
||||
const { setPrivateKey, setPublicKey } = useContext(UserContext)
|
||||
const { t } = useTranslation('common')
|
||||
const [isNip19, setIsNip19] = useState<boolean>(false)
|
||||
const [isPublic, setIsPublic] = useState<boolean>(false)
|
||||
const [inputValue, setInputValue] = useState<string>('')
|
||||
|
||||
useEffect(() => checkKey(), [inputValue])
|
||||
useEffect(() => {
|
||||
setPrivateKey(undefined)
|
||||
setPublicKey(undefined)
|
||||
}, [])
|
||||
|
||||
const checkKey: () => void = () => {
|
||||
if (inputValue && inputValue !== '') {
|
||||
@ -43,7 +35,7 @@ export const ProfileConnectPage: React.FC<ProfileConnectPageProps> = ({navigatio
|
||||
setPrivateKey(key)
|
||||
}
|
||||
|
||||
navigation.navigate('ProfileLoad')
|
||||
navigate('ProfileLoad')
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,7 +44,6 @@ export const ProfileConnectPage: React.FC<ProfileConnectPageProps> = ({navigatio
|
||||
Clipboard.getString().then((value) => {
|
||||
setInputValue(value ?? '')
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
const label: string = React.useMemo(() => isPublic ? t('loggerPage.publicKey') : t('loggerPage.privateKey'), [isPublic])
|
||||
@ -93,7 +84,7 @@ export const ProfileConnectPage: React.FC<ProfileConnectPageProps> = ({navigatio
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<Text>{t('loggerPage.notKeys')}</Text>
|
||||
<Button mode='text' onPress={() => navigation.navigate('ProfileCreate')}>
|
||||
<Button mode='text' onPress={() => navigate('ProfileCreate')}>
|
||||
{t('loggerPage.createButton')}
|
||||
</Button>
|
||||
</View>
|
||||
|
@ -4,8 +4,8 @@ import { Clipboard, StyleSheet, View } from 'react-native'
|
||||
import { Button, Snackbar, TextInput } from 'react-native-paper'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { nsecEncode } from 'nostr-tools/nip19'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { DrawerNavigationHelpers } from '@react-navigation/drawer/lib/typescript/src/types'
|
||||
import { UserContext } from '../../Contexts/UserContext'
|
||||
|
||||
interface ProfileCreatePageProps {
|
||||
navigation: DrawerNavigationHelpers;
|
||||
@ -13,7 +13,7 @@ interface ProfileCreatePageProps {
|
||||
|
||||
export const ProfileCreatePage: React.FC<ProfileCreatePageProps> = ({navigation}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { setPrivateKey } = useContext(RelayPoolContext)
|
||||
const { setPrivateKey } = useContext(UserContext)
|
||||
const [inputValue, setInputValue] = useState<string>()
|
||||
const [copied, setCopied] = useState<boolean>(false)
|
||||
|
||||
|
@ -2,31 +2,39 @@ import React, { useContext, useEffect, useState } from 'react'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { EventKind } from '../../lib/nostr/Events'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import { getUser, getUsers, User } from '../../Functions/DatabaseFunctions/Users'
|
||||
import { UserContext } from '../../Contexts/UserContext'
|
||||
import { getUsers, User } from '../../Functions/DatabaseFunctions/Users'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import moment from 'moment'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import Logo from '../../Components/Logo'
|
||||
import { Button, Snackbar, Text } from 'react-native-paper'
|
||||
import { DrawerNavigationHelpers } from '@react-navigation/drawer/lib/typescript/src/types'
|
||||
import { navigate } from '../../lib/Navigation'
|
||||
|
||||
interface ProfileLoadPageProps {
|
||||
navigation: DrawerNavigationHelpers;
|
||||
}
|
||||
|
||||
export const ProfileLoadPage: React.FC<ProfileLoadPageProps> = ({navigation}) => {
|
||||
export const ProfileLoadPage: React.FC = () => {
|
||||
const { loadingDb, database } = useContext(AppContext)
|
||||
const { publicKey, relayPool, lastEventId, loadingRelayPool } = useContext(RelayPoolContext)
|
||||
const { relayPool, lastEventId, loadingRelayPool } = useContext(RelayPoolContext)
|
||||
const { publicKey, reloadUser, user } = useContext(UserContext)
|
||||
const { t } = useTranslation('common')
|
||||
const [profileFound, setProfileFound] = useState<boolean>(false)
|
||||
const [contactsCount, setContactsCount] = useState<number>()
|
||||
const [contactsCount, setContactsCount] = useState<number>(0)
|
||||
|
||||
useEffect(() => {
|
||||
if (!loadingRelayPool && !loadingDb && publicKey) {
|
||||
relayPool?.subscribe('loading-meta', [
|
||||
{
|
||||
kinds: [EventKind.petNames, EventKind.meta],
|
||||
kinds: [EventKind.meta],
|
||||
authors: [publicKey],
|
||||
}
|
||||
])
|
||||
relayPool?.subscribe('loading-pets', [
|
||||
{
|
||||
kinds: [EventKind.petNames],
|
||||
authors: [publicKey],
|
||||
},
|
||||
{
|
||||
kinds: [EventKind.petNames],
|
||||
'#p': [publicKey],
|
||||
},
|
||||
])
|
||||
}
|
||||
@ -34,14 +42,19 @@ export const ProfileLoadPage: React.FC<ProfileLoadPageProps> = ({navigation}) =>
|
||||
|
||||
useEffect(() => {
|
||||
loadPets()
|
||||
loadProfile()
|
||||
reloadUser()
|
||||
}, [lastEventId])
|
||||
|
||||
useEffect(() => {
|
||||
if (user) setProfileFound(true)
|
||||
}, [user])
|
||||
|
||||
const loadPets: () => void = () => {
|
||||
if (database) {
|
||||
if (database && publicKey) {
|
||||
getUsers(database, { contacts: true }).then((results) => {
|
||||
setContactsCount(results.length)
|
||||
if (publicKey && results && results.length > 0) {
|
||||
if (results && results.length > 0) {
|
||||
reloadUser()
|
||||
setContactsCount(results.length)
|
||||
const authors = [...results.map((user: User) => user.id), publicKey]
|
||||
relayPool?.subscribe('loading-notes', [
|
||||
{
|
||||
@ -55,16 +68,6 @@ export const ProfileLoadPage: React.FC<ProfileLoadPageProps> = ({navigation}) =>
|
||||
}
|
||||
}
|
||||
|
||||
const loadProfile: () => void = () => {
|
||||
if (database && publicKey) {
|
||||
getUser(publicKey, database).then((result) => {
|
||||
if (result) {
|
||||
setProfileFound(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Logo onlyIcon size='medium'/>
|
||||
@ -74,15 +77,14 @@ export const ProfileLoadPage: React.FC<ProfileLoadPageProps> = ({navigation}) =>
|
||||
<Text variant='titleMedium'>
|
||||
{t('profileLoadPage.foundContacts', { contactsCount })}
|
||||
</Text>
|
||||
<Button mode='contained' onPress={() => navigation}>
|
||||
<Button mode='contained' onPress={() => navigate('Feed')}>
|
||||
{t('profileLoadPage.home')}
|
||||
</Button>
|
||||
|
||||
<Snackbar
|
||||
style={styles.snackbar}
|
||||
visible
|
||||
onDismiss={() => {}}
|
||||
action={{label: t('profileLoadPage.relays') ?? '', onPress: () => navigation.navigate('Relays')}}
|
||||
action={{label: t('profileLoadPage.relays') ?? '', onPress: () => navigate('Relays')}}
|
||||
>
|
||||
Conéctate a otros relays si tienes problemas encontrando tus datos.
|
||||
</Snackbar>
|
||||
|
@ -1,391 +1,17 @@
|
||||
import { Button, Card, Layout, Spinner, Text, TopNavigation, useTheme } from '@ui-kitten/components'
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react'
|
||||
import {
|
||||
Clipboard,
|
||||
NativeScrollEvent,
|
||||
NativeSyntheticEvent,
|
||||
RefreshControl,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
} from 'react-native'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import { getNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
|
||||
import NoteCard from '../../Components/NoteCard'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { getUser, User, updateUserContact } from '../../Functions/DatabaseFunctions/Users'
|
||||
import { EventKind } from '../../lib/nostr/Events'
|
||||
import Icon from 'react-native-vector-icons/FontAwesome5'
|
||||
import { formatPubKey, populatePets } from '../../Functions/RelayFunctions/Users'
|
||||
import { getReplyEventId } from '../../Functions/RelayFunctions/Events'
|
||||
import Loading from '../../Components/Loading'
|
||||
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
|
||||
import Avatar from '../../Components/Avatar'
|
||||
import { RelayFilters } from '../../lib/nostr/RelayPool/intex'
|
||||
import { t } from 'i18next'
|
||||
import TextContent from '../../Components/TextContent'
|
||||
import LnPayment from '../../Components/LnPayment'
|
||||
import React from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
|
||||
export const ProfilePage: React.FC = () => {
|
||||
const { database, page, goToPage, goBack } = useContext(AppContext)
|
||||
const { publicKey, lastEventId, relayPool } = useContext(RelayPoolContext)
|
||||
const theme = useTheme()
|
||||
const initialPageSize = 10
|
||||
const [notes, setNotes] = useState<Note[]>()
|
||||
const [user, setUser] = useState<User>()
|
||||
const [pageSize, setPageSize] = useState<number>(initialPageSize)
|
||||
const [isContact, setIsContact] = useState<boolean>()
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
const [openPayment, setOpenPayment] = useState<boolean>(false)
|
||||
const [firstLoad, setFirstLoad] = useState(true)
|
||||
const breadcrump = page.split('%')
|
||||
const userId = breadcrump[breadcrump.length - 1].split('#')[1] ?? publicKey
|
||||
const username = user?.name === '' ? formatPubKey(user.id) : user?.name
|
||||
|
||||
useEffect(() => {
|
||||
setRefreshing(true)
|
||||
setNotes(undefined)
|
||||
setUser(undefined)
|
||||
|
||||
loadUser()
|
||||
loadNotes()
|
||||
subscribeProfile()
|
||||
subscribeNotes()
|
||||
setFirstLoad(false)
|
||||
}, [page])
|
||||
|
||||
useEffect(() => {
|
||||
if (notes && !firstLoad) {
|
||||
loadUser()
|
||||
loadNotes()
|
||||
}
|
||||
}, [lastEventId])
|
||||
|
||||
useEffect(() => {
|
||||
if (pageSize > initialPageSize && !firstLoad) {
|
||||
loadUser()
|
||||
loadNotes()
|
||||
subscribeNotes(true)
|
||||
}
|
||||
}, [pageSize])
|
||||
|
||||
const loadUser: () => void = () => {
|
||||
if (database) {
|
||||
getUser(userId, database).then((result) => {
|
||||
if (result) {
|
||||
setUser(result)
|
||||
setIsContact(result?.contact)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const loadNotes: (past?: boolean) => void = () => {
|
||||
if (database) {
|
||||
getNotes(database, { filters: { pubkey: userId }, limit: pageSize }).then((results) => {
|
||||
setNotes(results)
|
||||
setRefreshing(false)
|
||||
relayPool?.subscribe('answers-profile', [
|
||||
{
|
||||
kinds: [EventKind.reaction],
|
||||
'#e': results.map((note) => note.id ?? ''),
|
||||
},
|
||||
])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const subscribeNotes: (past?: boolean) => void = (past) => {
|
||||
if (!database) return
|
||||
|
||||
const message: RelayFilters = {
|
||||
kinds: [EventKind.textNote, EventKind.recommendServer],
|
||||
authors: [userId],
|
||||
limit: pageSize,
|
||||
}
|
||||
relayPool?.subscribe('main-profile', [message])
|
||||
}
|
||||
|
||||
const subscribeProfile: () => Promise<void> = async () => {
|
||||
relayPool?.subscribe('user-profile', [
|
||||
{
|
||||
kinds: [EventKind.meta, EventKind.petNames],
|
||||
authors: [userId],
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
setRefreshing(true)
|
||||
relayPool?.unsubscribeAll()
|
||||
loadUser()
|
||||
loadNotes()
|
||||
subscribeProfile()
|
||||
subscribeNotes()
|
||||
}, [])
|
||||
|
||||
const removeAuthor: () => void = () => {
|
||||
if (relayPool && database && publicKey) {
|
||||
updateUserContact(userId, database, false).then(() => {
|
||||
populatePets(relayPool, database, publicKey)
|
||||
setIsContact(false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const addAuthor: () => void = () => {
|
||||
if (relayPool && database && publicKey) {
|
||||
updateUserContact(userId, database, true).then(() => {
|
||||
populatePets(relayPool, database, publicKey)
|
||||
setIsContact(true)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const renderOptions: () => JSX.Element = () => {
|
||||
const payment = user?.lnurl ? (
|
||||
<Button appearance='ghost' onPress={() => setOpenPayment(true)} status='warning'>
|
||||
<Icon name='bolt' size={16} color={theme['text-basic-color']} solid />
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
if (publicKey === userId) {
|
||||
return (
|
||||
<>
|
||||
{payment}
|
||||
<Button
|
||||
accessoryRight={<Icon name='cog' size={16} color={theme['text-basic-color']} solid />}
|
||||
onPress={() => goToPage('config')}
|
||||
appearance='ghost'
|
||||
/>
|
||||
</>
|
||||
)
|
||||
} else {
|
||||
if (user) {
|
||||
const contact = isContact ? (
|
||||
<Button
|
||||
accessoryRight={
|
||||
<Icon name='user-minus' size={16} color={theme['color-danger-500']} solid />
|
||||
}
|
||||
onPress={removeAuthor}
|
||||
appearance='ghost'
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
accessoryRight={
|
||||
<Icon name='user-plus' size={16} color={theme['color-success-500']} solid />
|
||||
}
|
||||
onPress={addAuthor}
|
||||
appearance='ghost'
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<>
|
||||
{payment}
|
||||
{contact}
|
||||
</>
|
||||
)
|
||||
} else {
|
||||
return <Spinner size='small' />
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onPressBack: () => void = () => {
|
||||
relayPool?.unsubscribeAll()
|
||||
goBack()
|
||||
}
|
||||
|
||||
const renderBackAction = (): JSX.Element => {
|
||||
if (publicKey === userId) {
|
||||
return <></>
|
||||
} else {
|
||||
return (
|
||||
<Button
|
||||
accessoryRight={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
||||
onPress={onPressBack}
|
||||
appearance='ghost'
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
list: {
|
||||
flex: 1,
|
||||
},
|
||||
icon: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
},
|
||||
settingsIcon: {
|
||||
width: 48,
|
||||
height: 48,
|
||||
},
|
||||
avatar: {
|
||||
width: 130,
|
||||
marginBottom: 16,
|
||||
},
|
||||
profile: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginBottom: 2,
|
||||
paddingLeft: 32,
|
||||
paddingRight: 32,
|
||||
},
|
||||
loading: {
|
||||
maxHeight: 160,
|
||||
},
|
||||
about: {
|
||||
flex: 4,
|
||||
maxHeight: 200,
|
||||
},
|
||||
stats: {
|
||||
flex: 1,
|
||||
},
|
||||
statsItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: 5,
|
||||
},
|
||||
description: {
|
||||
marginTop: 16,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
notCreated: {
|
||||
height: 64,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
spinner: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: 64,
|
||||
},
|
||||
})
|
||||
|
||||
const itemCard: (note: Note) => JSX.Element = (note) => {
|
||||
return (
|
||||
<Card onPress={() => onPressNote(note)} key={note.id ?? ''}>
|
||||
<NoteCard note={note} onlyContactsReplies={true} />
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const onPressNote: (note: Note) => void = (note) => {
|
||||
if (note.kind !== EventKind.recommendServer) {
|
||||
const mainEventId = getReplyEventId(note)
|
||||
if (mainEventId) {
|
||||
goToPage(`note#${mainEventId}`)
|
||||
} else if (note.id) {
|
||||
goToPage(`note#${note.id}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onPressId: () => void = () => {
|
||||
Clipboard.setString(user?.id ?? '')
|
||||
}
|
||||
|
||||
const onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void = (event) => {
|
||||
if (handleInfinityScroll(event)) {
|
||||
setPageSize(pageSize + initialPageSize)
|
||||
}
|
||||
}
|
||||
|
||||
const profile: JSX.Element = (
|
||||
<Layout style={styles.profile} level='3'>
|
||||
<Layout style={styles.avatar} level='3'>
|
||||
{user ? (
|
||||
<>
|
||||
<Avatar src={user?.picture} name={username} size={130} pubKey={user.id} />
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Layout>
|
||||
<TouchableOpacity onPress={onPressId}>
|
||||
<Text appearance='hint'>{user?.id}</Text>
|
||||
</TouchableOpacity>
|
||||
<Layout style={styles.description} level='3'>
|
||||
{user && (
|
||||
<>
|
||||
<Layout style={styles.about} level='3'>
|
||||
<TextContent content={user?.about} preview={false} />
|
||||
</Layout>
|
||||
</>
|
||||
)}
|
||||
</Layout>
|
||||
</Layout>
|
||||
)
|
||||
|
||||
const createProfile: JSX.Element = (
|
||||
<Layout style={styles.profile} level='3'>
|
||||
<Layout style={styles.notCreated} level='3'>
|
||||
<Text>{t('profilePage.profileNotCreated')}</Text>
|
||||
</Layout>
|
||||
<Button
|
||||
onPress={() => goToPage('config')}
|
||||
status='warning'
|
||||
accessoryLeft={<Icon name='cog' size={16} color={theme['text-basic-color']} solid />}
|
||||
>
|
||||
{t('profilePage.createProfile')}
|
||||
</Button>
|
||||
</Layout>
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<TopNavigation
|
||||
alignment='center'
|
||||
title={username}
|
||||
accessoryLeft={renderBackAction}
|
||||
accessoryRight={renderOptions}
|
||||
/>
|
||||
{!user && userId === publicKey ? createProfile : profile}
|
||||
<Layout style={styles.list} level='3'>
|
||||
{notes && notes.length > 0 ? (
|
||||
<ScrollView
|
||||
onScroll={onScroll}
|
||||
horizontal={false}
|
||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
|
||||
>
|
||||
{notes.map((note) => itemCard(note))}
|
||||
{notes.length >= 10 && (
|
||||
<Layout style={styles.spinner}>
|
||||
<Spinner size='small' />
|
||||
</Layout>
|
||||
)}
|
||||
</ScrollView>
|
||||
) : (
|
||||
<Loading />
|
||||
)}
|
||||
<LnPayment user={user} open={openPayment} setOpen={setOpenPayment} />
|
||||
</Layout>
|
||||
{publicKey === userId && (
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(0,0,0,0.2)',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: 65,
|
||||
position: 'absolute',
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
height: 65,
|
||||
backgroundColor: theme['color-warning-500'],
|
||||
borderRadius: 100,
|
||||
}}
|
||||
onPress={() => goToPage('contacts')}
|
||||
>
|
||||
<Icon name='address-book' size={30} color={theme['text-basic-color']} solid />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</>
|
||||
<View style={styles.container}>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
padding: 16,
|
||||
},
|
||||
})
|
||||
|
||||
export default ProfilePage
|
||||
|
342
frontend/Pages/ProfilePageOld/index.tsx
Normal file
342
frontend/Pages/ProfilePageOld/index.tsx
Normal file
@ -0,0 +1,342 @@
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react'
|
||||
import {
|
||||
Clipboard,
|
||||
NativeScrollEvent,
|
||||
NativeSyntheticEvent,
|
||||
RefreshControl,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
} from 'react-native'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import { getNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
|
||||
import NoteCard from '../../Components/NoteCard'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { getUser, User, updateUserContact } from '../../Functions/DatabaseFunctions/Users'
|
||||
import { EventKind } from '../../lib/nostr/Events'
|
||||
import Icon from 'react-native-vector-icons/FontAwesome5'
|
||||
import { formatPubKey, populatePets } from '../../Functions/RelayFunctions/Users'
|
||||
import { getReplyEventId } from '../../Functions/RelayFunctions/Events'
|
||||
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
|
||||
import Avatar from '../../Components/Avatar'
|
||||
import { RelayFilters } from '../../lib/nostr/RelayPool/intex'
|
||||
import { t } from 'i18next'
|
||||
import TextContent from '../../Components/TextContent'
|
||||
import LnPayment from '../../Components/LnPayment'
|
||||
|
||||
export const ProfilePage: React.FC = () => {
|
||||
const { database, page, goToPage, goBack } = useContext(AppContext)
|
||||
const { publicKey, lastEventId, relayPool } = useContext(RelayPoolContext)
|
||||
const theme = useTheme()
|
||||
const initialPageSize = 10
|
||||
const [notes, setNotes] = useState<Note[]>()
|
||||
const [user, setUser] = useState<User>()
|
||||
const [pageSize, setPageSize] = useState<number>(initialPageSize)
|
||||
const [isContact, setIsContact] = useState<boolean>()
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
const [openPayment, setOpenPayment] = useState<boolean>(false)
|
||||
const [firstLoad, setFirstLoad] = useState(true)
|
||||
const breadcrump = page.split('%')
|
||||
const userId = breadcrump[breadcrump.length - 1].split('#')[1] ?? publicKey
|
||||
const username = user?.name === '' ? formatPubKey(user.id) : user?.name
|
||||
|
||||
useEffect(() => {
|
||||
setRefreshing(true)
|
||||
setNotes(undefined)
|
||||
setUser(undefined)
|
||||
|
||||
loadUser()
|
||||
loadNotes()
|
||||
subscribeProfile()
|
||||
subscribeNotes()
|
||||
setFirstLoad(false)
|
||||
}, [page])
|
||||
|
||||
useEffect(() => {
|
||||
if (notes && !firstLoad) {
|
||||
loadUser()
|
||||
loadNotes()
|
||||
}
|
||||
}, [lastEventId])
|
||||
|
||||
useEffect(() => {
|
||||
if (pageSize > initialPageSize && !firstLoad) {
|
||||
loadUser()
|
||||
loadNotes()
|
||||
subscribeNotes(true)
|
||||
}
|
||||
}, [pageSize])
|
||||
|
||||
const loadUser: () => void = () => {
|
||||
if (database) {
|
||||
getUser(userId, database).then((result) => {
|
||||
if (result) {
|
||||
setUser(result)
|
||||
setIsContact(result?.contact)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const loadNotes: (past?: boolean) => void = () => {
|
||||
if (database) {
|
||||
getNotes(database, { filters: { pubkey: userId }, limit: pageSize }).then((results) => {
|
||||
setNotes(results)
|
||||
setRefreshing(false)
|
||||
relayPool?.subscribe('answers-profile', [
|
||||
{
|
||||
kinds: [EventKind.reaction],
|
||||
'#e': results.map((note) => note.id ?? ''),
|
||||
},
|
||||
])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const subscribeNotes: (past?: boolean) => void = (past) => {
|
||||
if (!database) return
|
||||
|
||||
const message: RelayFilters = {
|
||||
kinds: [EventKind.textNote, EventKind.recommendServer],
|
||||
authors: [userId],
|
||||
limit: pageSize,
|
||||
}
|
||||
relayPool?.subscribe('main-profile', [message])
|
||||
}
|
||||
|
||||
const subscribeProfile: () => Promise<void> = async () => {
|
||||
relayPool?.subscribe('user-profile', [
|
||||
{
|
||||
kinds: [EventKind.meta, EventKind.petNames],
|
||||
authors: [userId],
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
setRefreshing(true)
|
||||
relayPool?.unsubscribeAll()
|
||||
loadUser()
|
||||
loadNotes()
|
||||
subscribeProfile()
|
||||
subscribeNotes()
|
||||
}, [])
|
||||
|
||||
const removeAuthor: () => void = () => {
|
||||
if (relayPool && database && publicKey) {
|
||||
updateUserContact(userId, database, false).then(() => {
|
||||
populatePets(relayPool, database, publicKey)
|
||||
setIsContact(false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const addAuthor: () => void = () => {
|
||||
if (relayPool && database && publicKey) {
|
||||
updateUserContact(userId, database, true).then(() => {
|
||||
populatePets(relayPool, database, publicKey)
|
||||
setIsContact(true)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const renderOptions: () => JSX.Element = () => {
|
||||
const payment = user?.lnurl ? (
|
||||
// <Button appearance='ghost' onPress={() => setOpenPayment(true)} status='warning'>
|
||||
// <Icon name='bolt' size={16} color={theme['text-basic-color']} solid />
|
||||
// </Button>
|
||||
<></>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
if (publicKey === userId) {
|
||||
return (
|
||||
<>
|
||||
{payment}
|
||||
{/* <Button
|
||||
accessoryRight={<Icon name='cog' size={16} color={theme['text-basic-color']} solid />}
|
||||
onPress={() => goToPage('config')}
|
||||
appearance='ghost'
|
||||
/> */}
|
||||
<></>
|
||||
</>
|
||||
)
|
||||
} else {
|
||||
if (user) {
|
||||
const contact = isContact ? (
|
||||
// <Button
|
||||
// accessoryRight={
|
||||
// <Icon name='user-minus' size={16} color={theme['color-danger-500']} solid />
|
||||
// }
|
||||
// onPress={removeAuthor}
|
||||
// appearance='ghost'
|
||||
// />
|
||||
<></>
|
||||
) : (
|
||||
// <Button
|
||||
// accessoryRight={
|
||||
// <Icon name='user-plus' size={16} color={theme['color-success-500']} solid />
|
||||
// }
|
||||
// onPress={addAuthor}
|
||||
// appearance='ghost'
|
||||
// />
|
||||
<></>
|
||||
)
|
||||
return (
|
||||
<>
|
||||
{payment}
|
||||
{contact}
|
||||
</>
|
||||
)
|
||||
} else {
|
||||
return <Spinner size='small' />
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onPressBack: () => void = () => {
|
||||
relayPool?.unsubscribeAll()
|
||||
goBack()
|
||||
}
|
||||
|
||||
const renderBackAction = (): JSX.Element => {
|
||||
if (publicKey === userId) {
|
||||
return <></>
|
||||
} else {
|
||||
return (
|
||||
// <Button
|
||||
// accessoryRight={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
||||
// onPress={onPressBack}
|
||||
// appearance='ghost'
|
||||
// />
|
||||
<></>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const itemCard: (note: Note) => JSX.Element = (note) => {
|
||||
return (
|
||||
// <Card onPress={() => onPressNote(note)} key={note.id ?? ''}>
|
||||
// <NoteCard note={note} onlyContactsReplies={true} />
|
||||
// </Card>
|
||||
<></>
|
||||
)
|
||||
}
|
||||
|
||||
const onPressNote: (note: Note) => void = (note) => {
|
||||
if (note.kind !== EventKind.recommendServer) {
|
||||
const mainEventId = getReplyEventId(note)
|
||||
if (mainEventId) {
|
||||
goToPage(`note#${mainEventId}`)
|
||||
} else if (note.id) {
|
||||
goToPage(`note#${note.id}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onPressId: () => void = () => {
|
||||
Clipboard.setString(user?.id ?? '')
|
||||
}
|
||||
|
||||
const onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void = (event) => {
|
||||
if (handleInfinityScroll(event)) {
|
||||
setPageSize(pageSize + initialPageSize)
|
||||
}
|
||||
}
|
||||
|
||||
const profile: JSX.Element = (
|
||||
// <Layout style={styles.profile} level='3'>
|
||||
// <Layout style={styles.avatar} level='3'>
|
||||
// {user ? (
|
||||
// <>
|
||||
// <Avatar src={user?.picture} name={username} size={130} pubKey={user.id} />
|
||||
// </>
|
||||
// ) : (
|
||||
// <></>
|
||||
// )}
|
||||
// </Layout>
|
||||
// <TouchableOpacity onPress={onPressId}>
|
||||
// <Text appearance='hint'>{user?.id}</Text>
|
||||
// </TouchableOpacity>
|
||||
// <Layout style={styles.description} level='3'>
|
||||
// {user && (
|
||||
// <>
|
||||
// <Layout style={styles.about} level='3'>
|
||||
// <TextContent content={user?.about} preview={false} />
|
||||
// </Layout>
|
||||
// </>
|
||||
// )}
|
||||
// </Layout>
|
||||
// </Layout>
|
||||
<></>
|
||||
)
|
||||
|
||||
const createProfile: JSX.Element = (
|
||||
// <Layout style={styles.profile} level='3'>
|
||||
// <Layout style={styles.notCreated} level='3'>
|
||||
// <Text>{t('profilePage.profileNotCreated')}</Text>
|
||||
// </Layout>
|
||||
// <Button
|
||||
// onPress={() => goToPage('config')}
|
||||
// status='warning'
|
||||
// accessoryLeft={<Icon name='cog' size={16} color={theme['text-basic-color']} solid />}
|
||||
// >
|
||||
// {t('profilePage.createProfile')}
|
||||
// </Button>
|
||||
// </Layout>
|
||||
<></>
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <TopNavigation
|
||||
alignment='center'
|
||||
title={username}
|
||||
accessoryLeft={renderBackAction}
|
||||
accessoryRight={renderOptions}
|
||||
/>
|
||||
{!user && userId === publicKey ? createProfile : profile}
|
||||
<Layout style={styles.list} level='3'>
|
||||
{notes && notes.length > 0 ? (
|
||||
<ScrollView
|
||||
onScroll={onScroll}
|
||||
horizontal={false}
|
||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
|
||||
>
|
||||
{notes.map((note) => itemCard(note))}
|
||||
{notes.length >= 10 && (
|
||||
<Layout style={styles.spinner}>
|
||||
<Spinner size='small' />
|
||||
</Layout>
|
||||
)}
|
||||
</ScrollView>
|
||||
) : (
|
||||
<Loading />
|
||||
)}
|
||||
<LnPayment user={user} open={openPayment} setOpen={setOpenPayment} />
|
||||
</Layout>
|
||||
{publicKey === userId && (
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(0,0,0,0.2)',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: 65,
|
||||
position: 'absolute',
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
height: 65,
|
||||
backgroundColor: theme['color-warning-500'],
|
||||
borderRadius: 100,
|
||||
}}
|
||||
onPress={() => goToPage('contacts')}
|
||||
>
|
||||
<Icon name='address-book' size={30} color={theme['text-basic-color']} solid />
|
||||
</TouchableOpacity>
|
||||
)} */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProfilePage
|
@ -1,9 +1,8 @@
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import React, { useContext, useState } from 'react'
|
||||
import { Clipboard, FlatList, ListRenderItem, StyleSheet, View } from 'react-native'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { getRelays, Relay } from '../../Functions/DatabaseFunctions/Relays'
|
||||
import { Relay } from '../../Functions/DatabaseFunctions/Relays'
|
||||
import { defaultRelays, REGEX_SOCKET_LINK } from '../../Constants/Relay'
|
||||
import {
|
||||
Snackbar,
|
||||
@ -21,55 +20,35 @@ import RBSheet from 'react-native-raw-bottom-sheet'
|
||||
|
||||
export const RelaysPage: React.FC = () => {
|
||||
const defaultRelayInput = React.useMemo(() => 'wss://', [])
|
||||
const { database } = useContext(AppContext)
|
||||
const { relayPool, publicKey } = useContext(RelayPoolContext)
|
||||
const { addRelayItem, removeRelayItem, relays } = useContext(RelayPoolContext)
|
||||
const { t } = useTranslation('common')
|
||||
const theme = useTheme()
|
||||
const bottomSheetAddRef = React.useRef<RBSheet>(null)
|
||||
const bottomSheetEditRef = React.useRef<RBSheet>(null)
|
||||
const [relays, setRelays] = useState<Relay[]>([])
|
||||
const [selectedRelay, setSelectedRelay] = useState<Relay>()
|
||||
const [addRelayInput, setAddRelayInput] = useState<string>(defaultRelayInput)
|
||||
const [showNotification, setShowNotification] = useState<'remove' | 'add' | 'badFormat'>()
|
||||
|
||||
const loadRelays: () => void = () => {
|
||||
if (database) {
|
||||
getRelays(database).then((results) => {
|
||||
if (results) {
|
||||
setRelays(results)
|
||||
}
|
||||
})
|
||||
}
|
||||
const addRelay: (url: string) => void = (url) => {
|
||||
addRelayItem({
|
||||
url,
|
||||
}).then(() => {
|
||||
setShowNotification('add')
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(loadRelays, [])
|
||||
|
||||
const addRelayItem: (relay: Relay) => void = async (relay) => {
|
||||
if (relayPool && database && publicKey) {
|
||||
setRelays((prev) => [...prev, relay])
|
||||
relayPool.add(relay.url, () => {
|
||||
setShowNotification('add')
|
||||
loadRelays()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const removeRelayItem: (relay: Relay) => void = async (relay) => {
|
||||
if (relayPool && database && publicKey) {
|
||||
setRelays((prev) => prev.filter((item) => item.url !== relay.url))
|
||||
relayPool.remove(relay.url, () => {
|
||||
setShowNotification('remove')
|
||||
loadRelays()
|
||||
})
|
||||
}
|
||||
const removeRelay: (url: string) => void = (url) => {
|
||||
removeRelayItem({
|
||||
url,
|
||||
}).then(() => {
|
||||
setShowNotification('remove')
|
||||
})
|
||||
}
|
||||
|
||||
const onPressAddRelay: () => void = () => {
|
||||
if (REGEX_SOCKET_LINK.test(addRelayInput)) {
|
||||
bottomSheetAddRef.current?.close()
|
||||
addRelayItem({
|
||||
url: addRelayInput,
|
||||
})
|
||||
|
||||
setAddRelayInput(defaultRelayInput)
|
||||
} else {
|
||||
bottomSheetAddRef.current?.close()
|
||||
@ -91,7 +70,7 @@ export const RelaysPage: React.FC = () => {
|
||||
const active = relays?.some((item) => item.url === relay.url)
|
||||
|
||||
const onValueChange: () => void = () => {
|
||||
active ? removeRelayItem(relay) : addRelayItem(relay)
|
||||
active ? removeRelay(relay.url) : addRelay(relay.url)
|
||||
}
|
||||
|
||||
return <Switch value={active} onValueChange={onValueChange} />
|
||||
@ -109,6 +88,16 @@ export const RelaysPage: React.FC = () => {
|
||||
/>
|
||||
)
|
||||
|
||||
const rbSheetCustomStyles = React.useMemo(() => {
|
||||
return {
|
||||
container: {
|
||||
...styles.rbsheetContainer,
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
draggableIcon: styles.rbsheetDraggableIcon,
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<FlatList style={styles.list} data={[...relays, ...defaultList()]} renderItem={renderItem} />
|
||||
@ -130,18 +119,7 @@ export const RelaysPage: React.FC = () => {
|
||||
>
|
||||
{t(`relaysPage.${showNotification}`)}
|
||||
</Snackbar>
|
||||
<RBSheet
|
||||
ref={bottomSheetAddRef}
|
||||
closeOnDragDown={true}
|
||||
height={260}
|
||||
customStyles={{
|
||||
container: {
|
||||
...styles.rbsheetContainer,
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
draggableIcon: styles.rbsheetDraggableIcon,
|
||||
}}
|
||||
>
|
||||
<RBSheet ref={bottomSheetAddRef} closeOnDragDown={true} height={260} customStyles={rbSheetCustomStyles}>
|
||||
<View>
|
||||
<TextInput
|
||||
mode='outlined'
|
||||
@ -168,13 +146,7 @@ export const RelaysPage: React.FC = () => {
|
||||
ref={bottomSheetEditRef}
|
||||
closeOnDragDown={true}
|
||||
height={260}
|
||||
customStyles={{
|
||||
container: {
|
||||
...styles.rbsheetContainer,
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
draggableIcon: styles.rbsheetDraggableIcon,
|
||||
}}
|
||||
customStyles={rbSheetCustomStyles}
|
||||
>
|
||||
<View>
|
||||
<View style={styles.relayActions}>
|
||||
@ -183,7 +155,7 @@ export const RelaysPage: React.FC = () => {
|
||||
icon='trash-can-outline'
|
||||
size={28}
|
||||
onPress={() => {
|
||||
if (selectedRelay) removeRelayItem(selectedRelay)
|
||||
if (selectedRelay) removeRelay(selectedRelay.url)
|
||||
bottomSheetEditRef.current?.close()
|
||||
}}
|
||||
/>
|
||||
@ -200,7 +172,7 @@ export const RelaysPage: React.FC = () => {
|
||||
<Text>{t('relaysPage.copyRelay')}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Divider style={styles.divider}/>
|
||||
<Divider style={styles.divider} />
|
||||
<Text variant='titleLarge'>{selectedRelay?.url.split('wss://')[1]?.split('/')[0]}</Text>
|
||||
</View>
|
||||
</RBSheet>
|
||||
@ -238,12 +210,12 @@ const styles = StyleSheet.create({
|
||||
actionButton: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: 80
|
||||
width: 80,
|
||||
},
|
||||
divider: {
|
||||
marginBottom: 26,
|
||||
marginTop: 26
|
||||
}
|
||||
marginTop: 26,
|
||||
},
|
||||
})
|
||||
|
||||
export default RelaysPage
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React from 'react'
|
||||
import { AppContextProvider } from './Contexts/AppContext'
|
||||
import {
|
||||
InitialState,
|
||||
NavigationContainer,
|
||||
DefaultTheme as NavigationDefaultTheme,
|
||||
DarkTheme as NavigationDarkTheme,
|
||||
@ -13,15 +12,15 @@ import { adaptNavigationTheme, Provider as PaperProvider } from 'react-native-pa
|
||||
import { SafeAreaProvider, SafeAreaInsetsContext } from 'react-native-safe-area-context'
|
||||
import i18n from './i18n.config'
|
||||
import nostrosDarkTheme from './Constants/Theme/theme-dark.json'
|
||||
import { navigationRef } from './lib/Navigation'
|
||||
import HomeNavigator from './Pages/HomeNavigator'
|
||||
import MenuItems from './Components/MenuItems'
|
||||
import FeedNavigator from './Pages/FeedNavigator'
|
||||
import { UserContextProvider } from './Contexts/UserContext'
|
||||
|
||||
const DrawerNavigator = createDrawerNavigator()
|
||||
|
||||
export const Frontend: React.FC = () => {
|
||||
const [initialState] = React.useState<InitialState | undefined>()
|
||||
|
||||
const { DarkTheme } = adaptNavigationTheme({
|
||||
reactNavigationLight: NavigationDefaultTheme,
|
||||
reactNavigationDark: NavigationDarkTheme,
|
||||
@ -41,39 +40,42 @@ export const Frontend: React.FC = () => {
|
||||
<PaperProvider theme={nostrosDarkTheme}>
|
||||
<SafeAreaProvider>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<AppContextProvider>
|
||||
<RelayPoolContextProvider>
|
||||
<React.Fragment>
|
||||
<NavigationContainer theme={CombinedDefaultTheme} initialState={initialState}>
|
||||
<SafeAreaInsetsContext.Consumer>
|
||||
{() => {
|
||||
return (
|
||||
<DrawerNavigator.Navigator
|
||||
drawerContent={({navigation}) => <MenuItems navigation={navigation}/>}
|
||||
screenOptions={{
|
||||
drawerStyle: {
|
||||
borderRadius: 28
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DrawerNavigator.Screen
|
||||
name='Home'
|
||||
component={HomeNavigator}
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
<DrawerNavigator.Screen
|
||||
name='Feed'
|
||||
component={FeedNavigator}
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
</DrawerNavigator.Navigator>
|
||||
)
|
||||
}}
|
||||
</SafeAreaInsetsContext.Consumer>
|
||||
</NavigationContainer>
|
||||
</React.Fragment>
|
||||
</RelayPoolContextProvider>
|
||||
</AppContextProvider>
|
||||
<NavigationContainer theme={CombinedDefaultTheme} ref={navigationRef}>
|
||||
<AppContextProvider>
|
||||
<UserContextProvider>
|
||||
<RelayPoolContextProvider>
|
||||
<React.Fragment>
|
||||
<SafeAreaInsetsContext.Consumer>
|
||||
{() => {
|
||||
return (
|
||||
<DrawerNavigator.Navigator
|
||||
drawerContent={({ navigation }) => <MenuItems navigation={navigation} />}
|
||||
screenOptions={{
|
||||
drawerStyle: {
|
||||
borderRadius: 28,
|
||||
width: 296
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DrawerNavigator.Screen
|
||||
name='Home'
|
||||
component={HomeNavigator}
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
<DrawerNavigator.Screen
|
||||
name='Feed'
|
||||
component={FeedNavigator}
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
</DrawerNavigator.Navigator>
|
||||
)
|
||||
}}
|
||||
</SafeAreaInsetsContext.Consumer>
|
||||
</React.Fragment>
|
||||
</RelayPoolContextProvider>
|
||||
</UserContextProvider>
|
||||
</AppContextProvider>
|
||||
</NavigationContainer>
|
||||
</I18nextProvider>
|
||||
</SafeAreaProvider>
|
||||
</PaperProvider>
|
||||
|
15
frontend/lib/Navigation/index.ts
Normal file
15
frontend/lib/Navigation/index.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { createNavigationContainerRef } from '@react-navigation/native';
|
||||
|
||||
export const navigationRef = createNavigationContainerRef()
|
||||
|
||||
export const navigate: (name: string, params?: any) => void = (name, params ={}) => {
|
||||
if (navigationRef.isReady()) {
|
||||
navigationRef.navigate(name as never, params as never);
|
||||
}
|
||||
}
|
||||
|
||||
export const jumpTo: (name: string, params?: any) => void = (name, params ={}) => {
|
||||
if (navigationRef.isReady()) {
|
||||
navigationRef.jumpTo(name as never, params as never);
|
||||
}
|
||||
}
|
@ -39,6 +39,7 @@
|
||||
"react-native-multithreading": "^1.1.1",
|
||||
"react-native-paper": "^5.1.3",
|
||||
"react-native-parsed-text": "^0.0.22",
|
||||
"react-native-qrcode-svg": "^6.1.2",
|
||||
"react-native-quick-sqlite": "^6.1.1",
|
||||
"react-native-raw-bottom-sheet": "^2.2.0",
|
||||
"react-native-reanimated": "^2.14.0",
|
||||
@ -46,7 +47,7 @@
|
||||
"react-native-screens": "^3.18.2",
|
||||
"react-native-securerandom": "^1.0.1",
|
||||
"react-native-sensitive-info": "^5.5.8",
|
||||
"react-native-svg": "^13.5.0",
|
||||
"react-native-svg": "^13.7.0",
|
||||
"react-native-vector-icons": "^9.2.0",
|
||||
"react-native-webp-format": "^1.1.2",
|
||||
"readable-stream": "^4.3.0",
|
||||
|
41
yarn.lock
41
yarn.lock
@ -3452,6 +3452,11 @@ diffie-hellman@^5.0.0:
|
||||
miller-rabin "^4.0.0"
|
||||
randombytes "^2.0.0"
|
||||
|
||||
dijkstrajs@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.2.tgz#2e48c0d3b825462afe75ab4ad5e829c8ece36257"
|
||||
integrity sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg==
|
||||
|
||||
dir-glob@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
|
||||
@ -3549,6 +3554,11 @@ emoji-regex@^8.0.0:
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
|
||||
|
||||
encode-utf8@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda"
|
||||
integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==
|
||||
|
||||
encodeurl@~1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||
@ -6894,6 +6904,11 @@ pkg-dir@^4.2.0:
|
||||
dependencies:
|
||||
find-up "^4.0.0"
|
||||
|
||||
pngjs@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
|
||||
integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
|
||||
|
||||
posix-character-classes@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
|
||||
@ -7013,6 +7028,16 @@ punycode@^2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
qrcode@^1.5.0:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.1.tgz#0103f97317409f7bc91772ef30793a54cd59f0cb"
|
||||
integrity sha512-nS8NJ1Z3md8uTjKtP+SGGhfqmTCs5flU/xR623oI0JX+Wepz9R8UrRVCTBTJm3qGw3rH6jJ6MUHjkDx15cxSSg==
|
||||
dependencies:
|
||||
dijkstrajs "^1.0.1"
|
||||
encode-utf8 "^1.0.3"
|
||||
pngjs "^5.0.0"
|
||||
yargs "^15.3.1"
|
||||
|
||||
query-string@^7.1.3:
|
||||
version "7.1.3"
|
||||
resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328"
|
||||
@ -7148,6 +7173,14 @@ react-native-parsed-text@^0.0.22:
|
||||
dependencies:
|
||||
prop-types "^15.7.x"
|
||||
|
||||
react-native-qrcode-svg@^6.1.2:
|
||||
version "6.1.2"
|
||||
resolved "https://registry.yarnpkg.com/react-native-qrcode-svg/-/react-native-qrcode-svg-6.1.2.tgz#a7cb6c10199ab01418a7f7700ce17a6a014f544e"
|
||||
integrity sha512-lMbbxoPVybXCp9SYm73Aj/0iZ9OlSZl2u+zpdbjgC4DYHBm9m9tDQxISNg1OPeR7AAzmyx8IV4JTFmk8G5R22g==
|
||||
dependencies:
|
||||
prop-types "^15.7.2"
|
||||
qrcode "^1.5.0"
|
||||
|
||||
react-native-quick-sqlite@^6.1.1:
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-native-quick-sqlite/-/react-native-quick-sqlite-6.1.1.tgz#ba35d0a4a919a5a0962306c3fee4dc46983b0839"
|
||||
@ -7196,10 +7229,10 @@ react-native-sensitive-info@^5.5.8:
|
||||
resolved "https://registry.yarnpkg.com/react-native-sensitive-info/-/react-native-sensitive-info-5.5.8.tgz#6ebb67eed83d1c2867bd435630ef2c41eef204ed"
|
||||
integrity sha512-p99oaEW4QG1RdUNrkvd/c6Qdm856dQw/Rk81f9fA6Y3DlPs6ADNdU+jbPuTz3CcOUJwuKBDNenX6LR9KfmGFEg==
|
||||
|
||||
react-native-svg@^13.5.0:
|
||||
version "13.6.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-13.6.0.tgz#46e95a44aabbd778db7c46d8a1047da376b28058"
|
||||
integrity sha512-1wjHCMJ8siyZbDZ0MX5wM+Jr7YOkb6GADn4/Z+/u1UwJX8WfjarypxDF3UO1ugMHa+7qor39oY+URMcrgPpiww==
|
||||
react-native-svg@^13.7.0:
|
||||
version "13.7.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-13.7.0.tgz#be2ffb935e996762543dd7376bdc910722f7a43c"
|
||||
integrity sha512-WR5CIURvee5cAfvMhmdoeOjh1SC8KdLq5u5eFsz4pbYzCtIFClGSkLnNgkMSDMVV5LV0qQa4jeIk75ieIBzaDA==
|
||||
dependencies:
|
||||
css-select "^5.1.0"
|
||||
css-tree "^1.1.3"
|
||||
|
Loading…
Reference in New Issue
Block a user