Better login process

This commit is contained in:
KoalaSat 2023-02-08 17:09:13 +01:00
parent 7586c32306
commit 5738c2fbcb
No known key found for this signature in database
GPG Key ID: 2F7F61C6146AB157
24 changed files with 558 additions and 374 deletions

View File

@ -126,13 +126,13 @@ export const NoteCard: React.FC<NoteCardProps> = ({
}, [database]) }, [database])
const publishReaction: (positive: boolean) => void = (positive) => { const publishReaction: (positive: boolean) => void = (positive) => {
if (note && publicKey) { if (note?.id && publicKey) {
const event: Event = { const event: Event = {
content: positive ? '+' : '-', content: positive ? '+' : '-',
created_at: getUnixTime(new Date()), created_at: getUnixTime(new Date()),
kind: Kind.Reaction, kind: Kind.Reaction,
pubkey: publicKey, pubkey: publicKey,
tags: [...(note.tags ?? []), ['e', note.id], ['p', note.pubkey]], tags: [...note.tags, ['e', note.id], ['p', note.pubkey]],
} }
relayPool?.sendEvent(event) relayPool?.sendEvent(event)
} }

View File

@ -0,0 +1,296 @@
import { t } from 'i18next'
import * as React from 'react'
import { StyleSheet, View, ListRenderItem, Switch, FlatList } from 'react-native'
import { IconButton, List, Snackbar, Text, useTheme } from 'react-native-paper'
import { AppContext } from '../../Contexts/AppContext'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
import { UserContext } from '../../Contexts/UserContext'
import {
getUser,
updateUserBlock,
updateUserContact,
User,
} from '../../Functions/DatabaseFunctions/Users'
import { populatePets, username } from '../../Functions/RelayFunctions/Users'
import LnPayment from '../LnPayment'
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
import { navigate } from '../../lib/Navigation'
import RBSheet from 'react-native-raw-bottom-sheet'
import { getUserRelays, NoteRelay } from '../../Functions/DatabaseFunctions/NotesRelays'
import { relayToColor } from '../../Functions/NativeFunctions'
import { Relay } from '../../Functions/DatabaseFunctions/Relays'
import ProfileShare from '../ProfileShare'
interface ProfileActionsProps {
user: User
setUser: (user: User) => void
}
export const ProfileActions: React.FC<ProfileActionsProps> = ({ user, setUser }) => {
const theme = useTheme()
const { database } = React.useContext(AppContext)
const { publicKey } = React.useContext(UserContext)
const { relayPool, updateRelayItem } = React.useContext(RelayPoolContext)
const [isContact, setIsContact] = React.useState<boolean>()
const [showNotification, setShowNotification] = React.useState<undefined | string>()
const [showNotificationRelay, setShowNotificationRelay] = React.useState<undefined | string>()
const bottomSheetRelaysRef = React.useRef<RBSheet>(null)
const bottomSheetShareRef = React.useRef<RBSheet>(null)
const [userRelays, setUserRelays] = React.useState<NoteRelay[]>([])
const [openLn, setOpenLn] = React.useState<boolean>(false)
React.useEffect(() => {
loadUser()
loadRelays()
}, [])
const loadRelays: () => void = () => {
if (database) {
getUserRelays(database, user.id).then((results) => {
if (results) {
setUserRelays(results)
}
})
}
}
const loadUser: () => void = () => {
if (database) {
getUser(user.id, database).then((result) => {
if (result) {
setUser(result)
} else if (user.id === publicKey) {
setUser({
id: publicKey,
})
}
})
}
}
const onChangeBlockUser: () => void = () => {
if (database && user?.blocked !== undefined) {
updateUserBlock(user.id, database, !user?.blocked).then(() => {
loadUser()
})
}
}
const removeContact: () => void = () => {
if (relayPool && database && publicKey) {
updateUserContact(user.id, database, false).then(() => {
populatePets(relayPool, database, publicKey)
setIsContact(false)
setShowNotification('contactRemoved')
})
}
}
const addContact: () => void = () => {
if (relayPool && database && publicKey) {
updateUserContact(user.id, database, true).then(() => {
populatePets(relayPool, database, publicKey)
setIsContact(true)
setShowNotification('contactAdded')
})
}
}
const activeRelay: (relay: Relay) => void = (relay) => {
relay.active = 1
updateRelayItem(relay).then(() => {
setShowNotificationRelay('active')
})
}
const desactiveRelay: (relay: Relay) => void = (relay) => {
relay.active = 0
relay.global_feed = 0
updateRelayItem(relay).then(() => {
setShowNotificationRelay('desactive')
})
}
const bottomSheetStyles = React.useMemo(() => {
return {
container: {
backgroundColor: theme.colors.background,
paddingTop: 16,
paddingBottom: 32,
paddingLeft: 16,
borderTopRightRadius: 28,
borderTopLeftRadius: 28,
height: 'auto',
},
}
}, [])
const renderRelayItem: ListRenderItem<NoteRelay> = ({ index, item }) => {
return (
<List.Item
key={index}
title={item.url}
left={() => (
<MaterialCommunityIcons
style={styles.relayColor}
name='circle'
color={relayToColor(item.url)}
/>
)}
right={() => (
<Switch
style={styles.switch}
value={item.active !== undefined && item.active > 0}
onValueChange={() => (item.active ? desactiveRelay(item) : activeRelay(item))}
/>
)}
/>
)
}
return (
<View style={styles.container}>
<View style={styles.mainLayout}>
<View style={styles.actionButton}>
<IconButton
icon={isContact ? 'account-multiple-remove-outline' : 'account-multiple-plus-outline'}
size={28}
onPress={() => {
isContact ? removeContact() : addContact()
}}
disabled={user.id === publicKey}
/>
<Text>{isContact ? t('profilePage.unfollow') : t('profilePage.follow')}</Text>
</View>
<View style={styles.actionButton}>
<IconButton
icon='message-plus-outline'
size={28}
onPress={() => {
navigate('Conversation', {
pubKey: user.id,
title: username(user),
})
}}
/>
<Text>{t('profilePage.message')}</Text>
</View>
<View style={styles.actionButton}>
<IconButton
icon='lightning-bolt'
size={28}
onPress={() => setOpenLn(true)}
disabled={!user?.lnurl}
iconColor='#F5D112'
/>
<Text>{t('profilePage.invoice')}</Text>
</View>
</View>
<View style={styles.mainLayout}>
<View style={styles.actionButton}>
<IconButton
icon={user?.blocked && user?.blocked > 0 ? 'account-cancel' : 'account-cancel-outline'}
size={28}
onPress={onChangeBlockUser}
/>
<Text>
{t(user?.blocked && user?.blocked > 0 ? 'profileCard.unblock' : 'profileCard.block')}
</Text>
</View>
<View style={styles.actionButton}>
<IconButton
icon='share-variant-outline'
size={28}
onPress={() => bottomSheetShareRef.current?.open()}
/>
<Text>{t('profileCard.share')}</Text>
</View>
<View style={styles.actionButton}>
<IconButton
icon='chart-timeline-variant'
size={28}
onPress={() => bottomSheetRelaysRef.current?.open()}
/>
<Text>{t('profilePage.relaysTitle')}</Text>
</View>
</View>
<RBSheet ref={bottomSheetRelaysRef} closeOnDragDown={true} customStyles={bottomSheetStyles}>
<View>
<Text variant='titleLarge'>{t('profilePage.relaysTitle')}</Text>
<Text variant='bodyMedium'>
{t('profilePage.relaysDescription', { username: username(user) })}
</Text>
<List.Item
title={t('relaysPage.relayName')}
right={() => (
<>
<Text style={styles.listHeader}>{t('relaysPage.active')}</Text>
</>
)}
/>
<FlatList
showsVerticalScrollIndicator={false}
data={userRelays}
renderItem={renderRelayItem}
/>
</View>
{showNotificationRelay && (
<Snackbar
style={styles.snackbar}
visible={showNotificationRelay !== undefined}
duration={Snackbar.DURATION_SHORT}
onIconPress={() => setShowNotificationRelay(undefined)}
onDismiss={() => setShowNotificationRelay(undefined)}
>
{t(`profilePage.${showNotificationRelay}`)}
</Snackbar>
)}
</RBSheet>
<RBSheet ref={bottomSheetShareRef} closeOnDragDown={true} customStyles={bottomSheetStyles}>
<ProfileShare user={user} />
</RBSheet>
<LnPayment setOpen={setOpenLn} open={openLn} user={user} />
{showNotification && (
<Snackbar
style={styles.snackbar}
visible={showNotification !== undefined}
duration={Snackbar.DURATION_SHORT}
onIconPress={() => setShowNotification(undefined)}
onDismiss={() => setShowNotification(undefined)}
>
{t(`profilePage.${showNotification}`)}
</Snackbar>
)}
</View>
)
}
const styles = StyleSheet.create({
container: {
width: '100%',
},
relayColor: {
paddingTop: 9,
},
mainLayout: {
flexDirection: 'row',
justifyContent: 'space-between',
},
actionButton: {
alignItems: 'center',
},
snackbar: {
marginLeft: 16,
bottom: 16,
},
switch: {
marginLeft: 32,
},
listHeader: {
paddingRight: 5,
paddingLeft: 16,
textAlign: 'center',
},
})
export default ProfileActions

View File

@ -1,23 +1,17 @@
import { t } from 'i18next' import { t } from 'i18next'
import * as React from 'react' import * as React from 'react'
import { StyleSheet, View } from 'react-native' import { StyleSheet, View } from 'react-native'
import { Card, IconButton, Snackbar, Text, useTheme } from 'react-native-paper' import { Divider, Snackbar, TouchableRipple, useTheme } from 'react-native-paper'
import { AppContext } from '../../Contexts/AppContext' import { AppContext } from '../../Contexts/AppContext'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext' import { getUser, User } from '../../Functions/DatabaseFunctions/Users'
import { UserContext } from '../../Contexts/UserContext' import { usernamePubKey } from '../../Functions/RelayFunctions/Users'
import {
getUser,
updateUserBlock,
updateUserContact,
User,
} from '../../Functions/DatabaseFunctions/Users'
import { populatePets, usernamePubKey } from '../../Functions/RelayFunctions/Users'
import LnPayment from '../LnPayment' import LnPayment from '../LnPayment'
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons' import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
import { navigate, push } from '../../lib/Navigation' import { push } from '../../lib/Navigation'
import RBSheet from 'react-native-raw-bottom-sheet' import RBSheet from 'react-native-raw-bottom-sheet'
import { getNpub } from '../../lib/nostr/Nip19' import { getNpub } from '../../lib/nostr/Nip19'
import ProfileData from '../ProfileData' import ProfileData from '../ProfileData'
import ProfileActions from '../ProfileActions'
interface ProfileCardProps { interface ProfileCardProps {
bottomSheetRef: React.RefObject<RBSheet> bottomSheetRef: React.RefObject<RBSheet>
@ -27,13 +21,9 @@ interface ProfileCardProps {
export const ProfileCard: React.FC<ProfileCardProps> = ({ bottomSheetRef, showImages = true }) => { export const ProfileCard: React.FC<ProfileCardProps> = ({ bottomSheetRef, showImages = true }) => {
const theme = useTheme() const theme = useTheme()
const { displayUserDrawer } = React.useContext(AppContext) const { displayUserDrawer } = React.useContext(AppContext)
const { database, setDisplayUserShareDrawer } = React.useContext(AppContext) const { database } = React.useContext(AppContext)
const { publicKey } = React.useContext(UserContext)
const { relayPool } = React.useContext(RelayPoolContext)
const [user, setUser] = React.useState<User>() const [user, setUser] = React.useState<User>()
const [blocked, setBlocked] = React.useState<number>()
const [openLn, setOpenLn] = React.useState<boolean>(false) const [openLn, setOpenLn] = React.useState<boolean>(false)
const [isContact, setIsContact] = React.useState<boolean>()
const [showNotification, setShowNotification] = React.useState<undefined | string>() const [showNotification, setShowNotification] = React.useState<undefined | string>()
const nPub = React.useMemo(() => getNpub(displayUserDrawer), [displayUserDrawer]) const nPub = React.useMemo(() => getNpub(displayUserDrawer), [displayUserDrawer])
const username = React.useMemo(() => usernamePubKey(user?.name ?? '', nPub), [nPub, user]) const username = React.useMemo(() => usernamePubKey(user?.name ?? '', nPub), [nPub, user])
@ -42,45 +32,13 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({ bottomSheetRef, showIm
loadUser() loadUser()
}, []) }, [])
const onChangeBlockUser: () => void = () => {
if (database && blocked !== undefined && displayUserDrawer) {
updateUserBlock(displayUserDrawer, database, !blocked).then(() => {
setBlocked(blocked === 0 ? 1 : 0)
loadUser()
})
}
}
const removeContact: () => void = () => {
if (relayPool && database && publicKey && displayUserDrawer) {
updateUserContact(displayUserDrawer, database, false).then(() => {
populatePets(relayPool, database, publicKey)
setIsContact(false)
setShowNotification('contactRemoved')
})
}
}
const addContact: () => void = () => {
if (relayPool && database && publicKey && displayUserDrawer) {
updateUserContact(displayUserDrawer, database, true).then(() => {
populatePets(relayPool, database, publicKey)
setIsContact(true)
setShowNotification('contactAdded')
})
}
}
const loadUser: () => void = () => { const loadUser: () => void = () => {
if (database && displayUserDrawer) { if (database && displayUserDrawer) {
getUser(displayUserDrawer, database).then((result) => { getUser(displayUserDrawer, database).then((result) => {
if (result) { if (result) {
setUser(result) setUser(result)
setBlocked(result.blocked)
setIsContact(result?.contact)
} else { } else {
setUser({ id: displayUserDrawer }) setUser({ id: displayUserDrawer })
setBlocked(0)
} }
}) })
} }
@ -93,9 +51,10 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({ bottomSheetRef, showIm
return ( return (
<View> <View>
<Card onPress={goToProfile}> <Divider />
<Card.Content style={styles.card}> <TouchableRipple onPress={goToProfile}>
<View style={styles.cardUser}> <View style={styles.cardUser}>
<View>
<View style={styles.cardUserMain}> <View style={styles.cardUserMain}>
<ProfileData <ProfileData
username={user?.name} username={user?.name}
@ -107,81 +66,18 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({ bottomSheetRef, showIm
avatarSize={54} avatarSize={54}
/> />
</View> </View>
{user?.about && (
<View style={styles.about}>
<Text>
{`${user?.about ? user?.about?.slice(0, 75) : ''}${
user?.about && user?.about?.length > 75 ? ' ...' : ''
}`}
</Text>
</View>
)}
</View> </View>
<View> <View style={styles.arrow}>
<MaterialCommunityIcons <MaterialCommunityIcons
name='menu-right' name='menu-right'
size={25} size={25}
color={theme.colors.onPrimaryContainer} color={theme.colors.onPrimaryContainer}
/> />
</View> </View>
</Card.Content>
</Card>
<View style={styles.mainLayout}>
{displayUserDrawer !== publicKey && (
<View style={styles.actionButton}>
<IconButton
icon={isContact ? 'account-multiple-remove-outline' : 'account-multiple-plus-outline'}
size={28}
onPress={() => {
isContact ? removeContact() : addContact()
}}
/>
<Text>{isContact ? t('profileCard.unfollow') : t('profileCard.follow')}</Text>
</View>
)}
<View style={styles.actionButton}>
<IconButton
icon='message-plus-outline'
size={28}
onPress={() => {
navigate('Conversation', { pubKey: displayUserDrawer, title: username })
bottomSheetRef.current?.close()
}}
/>
<Text>{t('profileCard.message')}</Text>
</View> </View>
<View style={styles.actionButton}> </TouchableRipple>
<IconButton <Divider />
icon='share-variant-outline' {user && <ProfileActions user={user} setUser={setUser} />}
size={28}
onPress={() => {
setDisplayUserShareDrawer(user?.id)
}}
/>
<Text>{t('profileCard.share')}</Text>
</View>
{user?.lnurl && (
<View style={styles.actionButton}>
<>
<IconButton
icon='lightning-bolt'
size={28}
onPress={() => setOpenLn(true)}
iconColor='#F5D112'
/>
<Text>{t('profileCard.invoice')}</Text>
</>
</View>
)}
<View style={styles.actionButton}>
<IconButton
icon={blocked && blocked > 0 ? 'account-cancel' : 'account-cancel-outline'}
size={28}
onPress={onChangeBlockUser}
/>
<Text>{t(blocked && blocked > 0 ? 'profileCard.unblock' : 'profileCard.block')}</Text>
</View>
</View>
{showNotification && ( {showNotification && (
<Snackbar <Snackbar
style={styles.snackbar} style={styles.snackbar}
@ -229,6 +125,11 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
}, },
cardUser: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingTop: 16,
},
card: { card: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
@ -258,13 +159,18 @@ const styles = StyleSheet.create({
list: { list: {
padding: 16, padding: 16,
}, },
cardUser: {
flex: 1,
},
verifyIcon: { verifyIcon: {
paddingTop: 6, paddingTop: 6,
paddingLeft: 5, paddingLeft: 5,
}, },
dividerTop: {
marginBottom: 16,
},
arrow: {
alignContent: 'center',
justifyContent: 'center',
marginTop: -16,
},
}) })
export default ProfileCard export default ProfileCard

View File

@ -3,37 +3,21 @@ import * as React from 'react'
import { StyleSheet, View } from 'react-native' import { StyleSheet, View } from 'react-native'
import Clipboard from '@react-native-clipboard/clipboard' import Clipboard from '@react-native-clipboard/clipboard'
import { IconButton, Snackbar, Text, TouchableRipple } from 'react-native-paper' import { IconButton, Snackbar, Text, TouchableRipple } from 'react-native-paper'
import { AppContext } from '../../Contexts/AppContext' import { User } from '../../Functions/DatabaseFunctions/Users'
import { getUser, User } from '../../Functions/DatabaseFunctions/Users'
import Share from 'react-native-share' import Share from 'react-native-share'
import RBSheet from 'react-native-raw-bottom-sheet' import RBSheet from 'react-native-raw-bottom-sheet'
import { getNpub } from '../../lib/nostr/Nip19' import { getNpub } from '../../lib/nostr/Nip19'
import QRCode from 'react-native-qrcode-svg' import QRCode from 'react-native-qrcode-svg'
export const ProfileShare: React.FC = () => { interface ProfileShareProps {
const { displayUserShareDrawer } = React.useContext(AppContext) user: User
}
export const ProfileShare: React.FC<ProfileShareProps> = ({ user }) => {
const bottomSheetShareRef = React.useRef<RBSheet>(null) const bottomSheetShareRef = React.useRef<RBSheet>(null)
const { database } = React.useContext(AppContext)
const [user, setUser] = React.useState<User>()
const [qrCode, setQrCode] = React.useState<any>() const [qrCode, setQrCode] = React.useState<any>()
const [showNotification, setShowNotification] = React.useState<undefined | string>() const [showNotification, setShowNotification] = React.useState<undefined | string>()
const nPub = React.useMemo(() => getNpub(displayUserShareDrawer), [displayUserShareDrawer]) const nPub = React.useMemo(() => getNpub(user.id), [user])
React.useEffect(() => {
loadUser()
}, [])
const loadUser: () => void = () => {
if (database && displayUserShareDrawer) {
getUser(displayUserShareDrawer, database).then((result) => {
if (result) {
setUser(result)
} else {
setUser({ id: displayUserShareDrawer })
}
})
}
}
return ( return (
<View style={styles.mainLayout}> <View style={styles.mainLayout}>
@ -73,20 +57,19 @@ export const ProfileShare: React.FC = () => {
/> />
<Text>{t('profileShare.copyNPub')}</Text> <Text>{t('profileShare.copyNPub')}</Text>
</View> </View>
{user?.nip05 && ( <View style={styles.shareActionButton}>
<View style={styles.shareActionButton}> <IconButton
<IconButton icon='check-decagram-outline'
icon='check-decagram-outline' size={28}
size={28} onPress={() => {
onPress={() => { setShowNotification('nip05Copied')
setShowNotification('copyNip05') Clipboard.setString(user?.nip05 ?? '')
Clipboard.setString(user?.nip05 ?? '') bottomSheetShareRef.current?.close()
bottomSheetShareRef.current?.close() }}
}} disabled={!user.nip05}
/> />
<Text>{t('profileShare.copyNip05')}</Text> <Text>{t('profileShare.copyNip05')}</Text>
</View> </View>
)}
{showNotification && ( {showNotification && (
<Snackbar <Snackbar
style={styles.snackbar} style={styles.snackbar}

View File

@ -35,8 +35,6 @@ export interface AppContextProps {
checkClipboard: () => void checkClipboard: () => void
displayUserDrawer?: string displayUserDrawer?: string
setDisplayUserDrawer: (displayUserDrawer: string | undefined) => void setDisplayUserDrawer: (displayUserDrawer: string | undefined) => void
displayUserShareDrawer?: string
setDisplayUserShareDrawer: (displayUserShareDrawer: string | undefined) => void
refreshBottomBarAt?: number refreshBottomBarAt?: number
setRefreshBottomBarAt: (refreshBottomBarAt: number) => void setRefreshBottomBarAt: (refreshBottomBarAt: number) => void
pushedTab?: string pushedTab?: string
@ -78,7 +76,6 @@ export const initialAppContext: AppContextProps = {
getSatoshiSymbol: () => <></>, getSatoshiSymbol: () => <></>,
setClipboardNip21: () => {}, setClipboardNip21: () => {},
setDisplayUserDrawer: () => {}, setDisplayUserDrawer: () => {},
setDisplayUserShareDrawer: () => {},
} }
export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.Element => { export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.Element => {
@ -103,7 +100,6 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E
const [clipboardLoads, setClipboardLoads] = React.useState<string[]>([]) const [clipboardLoads, setClipboardLoads] = React.useState<string[]>([])
const [clipboardNip21, setClipboardNip21] = React.useState<string>() const [clipboardNip21, setClipboardNip21] = React.useState<string>()
const [displayUserDrawer, setDisplayUserDrawer] = React.useState<string>() const [displayUserDrawer, setDisplayUserDrawer] = React.useState<string>()
const [displayUserShareDrawer, setDisplayUserShareDrawer] = React.useState<string>()
const [pushedTab, setPushedTab] = useState<string>() const [pushedTab, setPushedTab] = useState<string>()
useEffect(() => { useEffect(() => {
@ -229,8 +225,6 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E
satoshi, satoshi,
setSatoshi, setSatoshi,
getSatoshiSymbol, getSatoshiSymbol,
setDisplayUserShareDrawer,
displayUserShareDrawer,
refreshBottomBarAt, refreshBottomBarAt,
setRefreshBottomBarAt, setRefreshBottomBarAt,
pushedTab, pushedTab,

View File

@ -1,10 +1,11 @@
import React, { useContext, useEffect, useMemo, useState } from 'react' import React, { useContext, useEffect, useMemo, useState } from 'react'
import RelayPool from '../lib/nostr/RelayPool/intex' import RelayPool, { fallbackRelays } from '../lib/nostr/RelayPool/intex'
import { AppContext } from './AppContext' import { AppContext } from './AppContext'
import { DeviceEventEmitter } from 'react-native' import { DeviceEventEmitter } from 'react-native'
import debounce from 'lodash.debounce' import debounce from 'lodash.debounce'
import { getRelays, Relay } from '../Functions/DatabaseFunctions/Relays' import { getActiveRelays, getRelays, Relay } from '../Functions/DatabaseFunctions/Relays'
import { UserContext } from './UserContext' import { UserContext } from './UserContext'
import { randomInt } from '../Functions/NativeFunctions'
export interface RelayPoolContextProps { export interface RelayPoolContextProps {
relayPoolReady: boolean relayPoolReady: boolean
@ -69,15 +70,25 @@ export const RelayPoolContextProvider = ({
[setLastConfirmationId], [setLastConfirmationId],
) )
const createRandomRelays: () => Promise<void> = async () => {
const randomrelays: string[] = []
while (randomrelays.length < 5) {
const index = randomInt(0, fallbackRelays.length - 1)
const url = fallbackRelays[index]
if (!randomrelays.includes(url)) {
randomrelays.push(fallbackRelays[index])
await addRelayItem({ url })
}
}
}
const loadRelayPool: () => void = async () => { const loadRelayPool: () => void = async () => {
if (database && publicKey) { if (database && publicKey) {
DeviceEventEmitter.addListener('WebsocketEvent', debouncedEventIdHandler)
DeviceEventEmitter.addListener('WebsocketConfirmation', debouncedConfirmationHandler)
const initRelayPool = new RelayPool(privateKey) const initRelayPool = new RelayPool(privateKey)
await initRelayPool.resilientMode(database, publicKey) initRelayPool.connect(publicKey, () => {
initRelayPool.connect(publicKey, () => setRelayPoolReady(true)) initRelayPool.resilientMode(database, publicKey)
setRelayPool(initRelayPool) setRelayPool(initRelayPool)
loadRelays() })
} }
} }
@ -137,10 +148,23 @@ export const RelayPoolContextProvider = ({
useEffect(() => { useEffect(() => {
if (publicKey && publicKey !== '') { if (publicKey && publicKey !== '') {
DeviceEventEmitter.addListener('WebsocketEvent', debouncedEventIdHandler)
DeviceEventEmitter.addListener('WebsocketConfirmation', debouncedConfirmationHandler)
loadRelayPool() loadRelayPool()
} }
}, [publicKey]) }, [publicKey])
useEffect(() => {
if (database && relayPool) {
getActiveRelays(database).then((results) => {
if (results.length < 1) {
createRandomRelays()
}
loadRelays().then(() => setRelayPoolReady(true))
})
}
}, [relayPool])
return ( return (
<RelayPoolContext.Provider <RelayPoolContext.Provider
value={{ value={{

View File

@ -53,7 +53,7 @@ export const initialUserContext: UserContextProps = {
export const UserContextProvider = ({ children }: UserContextProviderProps): JSX.Element => { export const UserContextProvider = ({ children }: UserContextProviderProps): JSX.Element => {
const { database, loadingDb, init } = useContext(AppContext) const { database, loadingDb, init } = useContext(AppContext)
const { relayPool } = useContext(RelayPoolContext) const { relayPool, relayPoolReady } = useContext(RelayPoolContext)
const [userState, setUserState] = useState<'loading' | 'access' | 'ready'>('loading') const [userState, setUserState] = useState<'loading' | 'access' | 'ready'>('loading')
const [publicKey, setPublicKey] = useState<string>() const [publicKey, setPublicKey] = useState<string>()
const [nPub, setNpub] = useState<string>() const [nPub, setNpub] = useState<string>()
@ -123,10 +123,10 @@ export const UserContextProvider = ({ children }: UserContextProviderProps): JSX
}, [publicKey]) }, [publicKey])
useEffect(() => { useEffect(() => {
if (userState === 'ready' && publicKey) { if (userState === 'ready' && publicKey && relayPoolReady) {
navigate('Feed') navigate('Feed')
} }
}, [userState, publicKey]) }, [userState, publicKey, relayPoolReady])
useEffect(() => { useEffect(() => {
if (!loadingDb) { if (!loadingDb) {

View File

@ -1,10 +1,34 @@
import { QuickSQLiteConnection } from 'react-native-quick-sqlite' import { QuickSQLiteConnection } from 'react-native-quick-sqlite'
import { getItems } from '..'
import { Relay } from '../Relays'
export interface NoteRelay { export interface NoteRelay extends Relay {
relay_url: string relay_url: string
pubkey: string pubkey: string
note_id: number note_id: number
} }
const databaseToEntity: (object: object) => NoteRelay = (object) => {
return object as NoteRelay
}
export const getUserRelays: (
db: QuickSQLiteConnection,
pubKey: string,
) => Promise<NoteRelay[]> = async (db, pubKey) => {
const query = `
SELECT * FROM nostros_notes_relays LEFT JOIN
nostros_relays ON nostros_relays.url = nostros_notes_relays.relay_url
WHERE pubkey = ? GROUP BY relay_url
`
const resultSet = db.execute(query, [pubKey])
if (resultSet.rows && resultSet.rows.length > 0) {
const items: object[] = getItems(resultSet)
const users: NoteRelay[] = items.map((object) => databaseToEntity(object))
return users
} else {
return []
}
}
export const getNoteRelaysUsage: ( export const getNoteRelaysUsage: (
db: QuickSQLiteConnection, db: QuickSQLiteConnection,

View File

@ -33,8 +33,8 @@ export const searchRelays: (
const searchQuery = `SELECT * FROM nostros_relays WHERE url = '${relayUrl}';` const searchQuery = `SELECT * FROM nostros_relays WHERE url = '${relayUrl}';`
const results = await db.execute(searchQuery) const results = await db.execute(searchQuery)
const items: object[] = getItems(results) const items: object[] = getItems(results)
const notes: Relay[] = items.map((object) => databaseToEntity(object)) const relays: Relay[] = items.map((object) => databaseToEntity(object))
return notes return relays
} }
export const getRelays: (db: QuickSQLiteConnection) => Promise<Relay[]> = async (db) => { export const getRelays: (db: QuickSQLiteConnection) => Promise<Relay[]> = async (db) => {
@ -45,6 +45,14 @@ export const getRelays: (db: QuickSQLiteConnection) => Promise<Relay[]> = async
return relays return relays
} }
export const getActiveRelays: (db: QuickSQLiteConnection) => Promise<Relay[]> = async (db) => {
const notesQuery = 'SELECT * FROM nostros_relays WHERE active = 1;'
const resultSet = await db.execute(notesQuery)
const items: object[] = getItems(resultSet)
const relays: Relay[] = items.map((object) => databaseToEntity(object))
return relays
}
export const getRelay: (db: QuickSQLiteConnection, url: string) => Promise<Relay> = async ( export const getRelay: (db: QuickSQLiteConnection, url: string) => Promise<Relay> = async (
db, db,
url, url,

View File

@ -8,7 +8,7 @@ export interface User {
picture?: string picture?: string
about?: string about?: string
contact?: boolean contact?: boolean
follower?: boolean follower?: number
lnurl?: string lnurl?: string
nip05?: string nip05?: string
created_at?: number created_at?: number

View File

@ -51,7 +51,7 @@ export const pickRandomItems = <T extends unknown>(arr: T[], n: number): T[] =>
export const validImageUrl: (url: string | undefined) => boolean = (url) => { export const validImageUrl: (url: string | undefined) => boolean = (url) => {
if (url) { if (url) {
const regexp = /^(https?:\/\/.*\.(?:png|jpg|jpeg|gif|webp))$/ const regexp = /^(https?:\/\/.*\.(?:png|jpg|jpeg|gif|webp)(\?.*)?)$/
return regexp.test(url) return regexp.test(url)
} else { } else {
return false return false

View File

@ -38,7 +38,13 @@ export const formatPubKey: (pubKey: string | undefined) => string = (pubKey) =>
const uniqueCode = pubKey.replace('npub1', '') const uniqueCode = pubKey.replace('npub1', '')
return `${uniqueCode.slice(0, 8)}:${uniqueCode.slice(-8)}` return formatId(uniqueCode)
}
export const formatId: (key: string | undefined) => string = (key) => {
if (!key) return ''
return `${key.slice(0, 8)}:${key.slice(-8)}`
} }
export const getNip05Domain: (nip05: string | undefined) => string | null = (nip05) => { export const getNip05Domain: (nip05: string | undefined) => string | null = (nip05) => {

View File

@ -41,7 +41,8 @@
"home": "Start", "home": "Start",
"searchingProfile": "Durchsuche Profil", "searchingProfile": "Durchsuche Profil",
"foundProfile": "Profil gefunden", "foundProfile": "Profil gefunden",
"foundContacts": "{{contactsCount}} Kontakte gefunden" "foundContacts": "{{contactsCount}} Kontakte gefunden",
"storing": "Storing {{lastEventId}} on database."
}, },
"sendPage": { "sendPage": {
"isContact": "Folge ich", "isContact": "Folge ich",
@ -257,13 +258,18 @@
"notifications": { "notifications": {
"contactAdded": "Kontakt hinzugefügt", "contactAdded": "Kontakt hinzugefügt",
"contactRemoved": "Entfernt ", "contactRemoved": "Entfernt ",
"npubCopied": "Öffentlicher Schlüssel kopiert." "npubCopied": "Öffentlicher Schlüssel kopiert.",
"active": "Relay aktiviert.",
"desactive": "Relay deaktiviert."
}, },
"invoice": "Zap", "invoice": "Zap",
"message": "Nachricht", "message": "Nachricht",
"follow": "Folgen", "follow": "Folgen",
"isFollower": "follows you",
"unfollow": "Nicht mehr folgen", "unfollow": "Nicht mehr folgen",
"copyNPub": "Schlüssel kopieren" "copyNPub": "Schlüssel kopieren",
"relaysTitle": "Relays",
"relaysDescription": "These are {{username}}'s relays, activate the ones you want to be connected."
}, },
"homePage": { "homePage": {
"clipboardTitle": "Nostr Schlüssel entdeckt", "clipboardTitle": "Nostr Schlüssel entdeckt",

View File

@ -40,8 +40,9 @@
"relays": "See relays", "relays": "See relays",
"home": "Go Home", "home": "Go Home",
"searchingProfile": "Searching for your profile", "searchingProfile": "Searching for your profile",
"foundProfile": "Found profile", "foundProfile": "Profile found",
"foundContacts": "{{contactsCount}} contacts found" "foundContacts": "{{contactsCount}} contacts found",
"storing": "Storing {{lastEventId}} on database."
}, },
"sendPage": { "sendPage": {
"isContact": "Following", "isContact": "Following",
@ -175,9 +176,9 @@
}, },
"relaysPage": { "relaysPage": {
"resilienceTitle": "Resilience (experimental)", "resilienceTitle": "Resilience (experimental)",
"resilienceDescription": "The nostr protocol provides a good amount of tools to build a desentralized network, but unlike other desentralized protocols such as Bitcoin, it's not inherent by defaul on his usage.\n\nTo ahcieve such goal, clients and relays should activally colaborate. Nostros resiliense mode tries to, first, expose these concerns to their users, and secondly, start experimening with new pattern and way to help on network' resilency.\n\nFunctional implementation will soon endup appearing on Nostros by default.", "resilienceDescription": "The Nostr protocol provides a good number of tools to build a decentralized network, but unlike other decentralized protocols such as Bitcoin, it is not inherently bound to its use. To achieve the goal of a decentralized network, clients (e.g., Nostros) and relays (content servers) should actively cooperate. The Nostros resilience mode seeks first to raise awareness of this among users, and second to test new patterns and methods to improve the resilience of the network.\nA working implementation for this will soon be applied in Nostros.",
"resilienceCategories": "Resilience exposure", "resilienceCategories": "Resilience exposure",
"resilienceCategoriesDescription": "A first approach Nostros is testing is randomly calculate (based on received contats' events) what are the most resilient relays. It tries to avoid both relays with too much events (centralization) and relays with almost no events (battery draining).\n\nThe goal is to generate a list of 5 relays and cover all contacts. But if this is not possible, it will search among other relays, warning to the user about it.\n\nKeep into consideration relays flaged as 'Centralized'/'Small' doesn't means they are to the network, but just to your data.", "resilienceCategoriesDescription": "An initial approach that Nostros is testing is to randomly calculate (based on received contact events) which relays are most balanced. This tries to avoid both relays with too many events (centralization) and relays with almost no events (battery discharge).\n\nThe goal is to create a list of 5 relays that cover all contacts. However, if this is not possible, Nostros will search among other relays and warn the user about it.",
"centralized": "Centralized", "centralized": "Centralized",
"small": "Small", "small": "Small",
"contacts": "# Contacts", "contacts": "# Contacts",
@ -269,13 +270,18 @@
"notifications": { "notifications": {
"contactAdded": "Profile followed", "contactAdded": "Profile followed",
"contactRemoved": "Profile unfollowed", "contactRemoved": "Profile unfollowed",
"npubCopied": "Public key copied." "npubCopied": "Public key copied.",
"active": "Relay activated.",
"desactive": "Relay desactivated."
}, },
"invoice": "Tip", "invoice": "Tip",
"message": "Message", "message": "Message",
"follow": "Follow", "follow": "Follow",
"unfollow": "Following", "unfollow": "Following",
"copyNPub": "Copy key" "copyNPub": "Copy key",
"relaysTitle": "Relays",
"isFollower": "follows you",
"relaysDescription": "These are {{username}}'s relays, activate the ones you want to be connected."
}, },
"homePage": { "homePage": {
"clipboardTitle": "Nostr key detected", "clipboardTitle": "Nostr key detected",

View File

@ -41,7 +41,8 @@
"home": "Acceder", "home": "Acceder",
"searchingProfile": "Buscando tu perfil", "searchingProfile": "Buscando tu perfil",
"foundProfile": "Perfil encontrado", "foundProfile": "Perfil encontrado",
"foundContacts": "{{contactsCount}} contactos encontrados" "foundContacts": "{{contactsCount}} contactos encontrados",
"storing": "Guardando {{lastEventId}} en base de datos."
}, },
"sendPage": { "sendPage": {
"isContact": "Siguiendo", "isContact": "Siguiendo",
@ -251,13 +252,18 @@
"notifications": { "notifications": {
"contactAdded": "Siguiendo", "contactAdded": "Siguiendo",
"contactRemoved": "Dejando de seguir ", "contactRemoved": "Dejando de seguir ",
"npubCopied": "Clave pública copiada." "npubCopied": "Clave pública copiada.",
"active": "Relay activado.",
"desactive": "Relay desactivado."
}, },
"invoice": "Propina", "invoice": "Propina",
"message": "Mensaje", "message": "Mensaje",
"isFollower": "te sigue",
"follow": "Seguir", "follow": "Seguir",
"unfollow": "Siguiendo", "unfollow": "Siguiendo",
"copyNPub": "Copiar clave" "copyNPub": "Copiar clave",
"relaysTitle": "Relays",
"relaysDescription": "Estos son los relays de {{username}}, activa aquellos a los que quieras estar conectado."
}, },
"homePage": { "homePage": {
"clipboardTitle": "Se ha detectado una clave Nostr", "clipboardTitle": "Se ha detectado una clave Nostr",

View File

@ -41,7 +41,8 @@
"home": "Accès", "home": "Accès",
"searchingProfile": "A la recherche de votre profil", "searchingProfile": "A la recherche de votre profil",
"foundProfile": "Profil trouvé", "foundProfile": "Profil trouvé",
"foundContacts": "{{contactsCount}} contacts trouvés" "foundContacts": "{{contactsCount}} contacts trouvés",
"storing": "Storing {{lastEventId}} on database."
}, },
"sendPage": { "sendPage": {
"isContact": "Abonné", "isContact": "Abonné",
@ -252,13 +253,18 @@
"notifications": { "notifications": {
"contactAdded": "Abonné", "contactAdded": "Abonné",
"contactRemoved": "Vous ne suivez plus ce profil", "contactRemoved": "Vous ne suivez plus ce profil",
"npubCopied": "Clé publique copiée" "npubCopied": "Clé publique copiée",
"active": "Actif",
"share": "Partager"
}, },
"invoice": "Facture", "invoice": "Facture",
"message": "Message", "message": "Message",
"follow": "Suivre", "follow": "Suivre",
"isFollower": "follows you",
"unfollow": "Abonné", "unfollow": "Abonné",
"copyNPub": "Copier la clé" "copyNPub": "Copier la clé",
"relaysTitle": "Relays",
"relaysDescription": "These are {{username}}'s relays, activate the ones you want to be connected."
}, },
"profileShare": { "profileShare": {
"notifications": { "notifications": {

View File

@ -41,7 +41,8 @@
"home": "На главную", "home": "На главную",
"searchingProfile": "Ищем Ваш профиль.", "searchingProfile": "Ищем Ваш профиль.",
"foundProfile": "Профиль найден.", "foundProfile": "Профиль найден.",
"foundContacts": "{{contactsCount}} контакта(ов) найдено" "foundContacts": "{{contactsCount}} контакта(ов) найдено",
"storing": "Storing {{lastEventId}} on database."
}, },
"sendPage": { "sendPage": {
"isContact": "Подписаны", "isContact": "Подписаны",
@ -219,7 +220,9 @@
"lud06Published": "LUD-06 published.\n\n{{lud06}}", "lud06Published": "LUD-06 published.\n\n{{lud06}}",
"nip05Published": "NIP-05 published.\n\n{{nip05}}", "nip05Published": "NIP-05 published.\n\n{{nip05}}",
"picturePublished": "Picture published.", "picturePublished": "Picture published.",
"connectionError": "Ошибка с подключением" "connectionError": "Ошибка с подключением",
"active": "Relay actived.",
"desactive": "Relay desactivated."
}, },
"publishLud06": "Опубликовть", "publishLud06": "Опубликовть",
"lud06Label": "LNURL / Lightning Address", "lud06Label": "LNURL / Lightning Address",
@ -257,7 +260,10 @@
"message": "Сообщение", "message": "Сообщение",
"follow": "Follow", "follow": "Follow",
"unfollow": "Following", "unfollow": "Following",
"copyNPub": "Скопировать ключ" "isFollower": "follows you",
"copyNPub": "Скопировать ключ",
"relaysTitle": "Relays",
"relaysDescription": "These are {{username}}'s relays, activate the ones you want to be connected."
}, },
"homePage": { "homePage": {
"clipboardTitle": "Se ha detectado una clave Nostr", "clipboardTitle": "Se ha detectado una clave Nostr",

View File

@ -40,7 +40,8 @@
"home": "去首页", "home": "去首页",
"searchingProfile": "搜索用户", "searchingProfile": "搜索用户",
"foundProfile": "已找到的用户", "foundProfile": "已找到的用户",
"foundContacts": "已找到 {{contactsCount}} 个联系人" "foundContacts": "已找到 {{contactsCount}} 个联系人",
"storing": "Storing {{lastEventId}} on database."
}, },
"sendPage": { "sendPage": {
"isContact": "正在关注", "isContact": "正在关注",
@ -232,7 +233,9 @@
"lud06Published": "LUD-06 已发布 \n\n{{lud06}}", "lud06Published": "LUD-06 已发布 \n\n{{lud06}}",
"nip05Published": "NIP-05 已发布 \n\n{{nip05}}", "nip05Published": "NIP-05 已发布 \n\n{{nip05}}",
"picturePublished": "头像已发布", "picturePublished": "头像已发布",
"connectionError": "连接错误" "connectionError": "连接错误",
"active": "中继已启用",
"desactive": "中继已停用"
}, },
"publishLud06": "发布", "publishLud06": "发布",
"lud06Label": "LNURL / Lightning 地址", "lud06Label": "LNURL / Lightning 地址",
@ -269,8 +272,11 @@
"invoice": "赞赏", "invoice": "赞赏",
"message": "私信", "message": "私信",
"follow": "关注", "follow": "关注",
"isFollower": "follows you",
"unfollow": "关注中", "unfollow": "关注中",
"copyNPub": "复制公钥" "copyNPub": "复制公钥",
"relaysTitle": "Relays",
"relaysDescription": "These are {{username}}'s relays, activate the ones you want to be connected."
}, },
"homePage": { "homePage": {
"clipboardTitle": "检测到 Nostr 公钥", "clipboardTitle": "检测到 Nostr 公钥",

View File

@ -18,7 +18,6 @@ import ConfigPage from '../ConfigPage'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext' import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
import { AppContext } from '../../Contexts/AppContext' import { AppContext } from '../../Contexts/AppContext'
import RelayCard from '../../Components/RelayCard' import RelayCard from '../../Components/RelayCard'
import ProfileShare from '../../Components/ProfileShare'
import { updateAllRead } from '../../Functions/DatabaseFunctions/DirectMessages' import { updateAllRead } from '../../Functions/DatabaseFunctions/DirectMessages'
import { getUnixTime } from 'date-fns' import { getUnixTime } from 'date-fns'
@ -26,18 +25,11 @@ export const HomeNavigator: React.FC = () => {
const theme = useTheme() const theme = useTheme()
const { t } = useTranslation('common') const { t } = useTranslation('common')
const { displayRelayDrawer, setDisplayrelayDrawer } = React.useContext(RelayPoolContext) const { displayRelayDrawer, setDisplayrelayDrawer } = React.useContext(RelayPoolContext)
const { const { displayUserDrawer, setDisplayUserDrawer, setRefreshBottomBarAt, database } =
displayUserDrawer, React.useContext(AppContext)
setDisplayUserDrawer,
displayUserShareDrawer,
setDisplayUserShareDrawer,
setRefreshBottomBarAt,
database,
} = React.useContext(AppContext)
const bottomSheetRef = React.useRef<RBSheet>(null) const bottomSheetRef = React.useRef<RBSheet>(null)
const bottomSheetProfileRef = React.useRef<RBSheet>(null) const bottomSheetProfileRef = React.useRef<RBSheet>(null)
const bottomSheetRelayRef = React.useRef<RBSheet>(null) const bottomSheetRelayRef = React.useRef<RBSheet>(null)
const bottomSheetShareRef = React.useRef<RBSheet>(null)
const Stack = React.useMemo(() => createStackNavigator(), []) const Stack = React.useMemo(() => createStackNavigator(), [])
const cardStyleInterpolator = React.useMemo( const cardStyleInterpolator = React.useMemo(
() => () =>
@ -78,10 +70,6 @@ export const HomeNavigator: React.FC = () => {
if (displayUserDrawer) bottomSheetProfileRef.current?.open() if (displayUserDrawer) bottomSheetProfileRef.current?.open()
}, [displayUserDrawer]) }, [displayUserDrawer])
React.useEffect(() => {
if (displayUserShareDrawer) bottomSheetShareRef.current?.open()
}, [displayUserShareDrawer])
return ( return (
<> <>
<Stack.Navigator <Stack.Navigator
@ -174,14 +162,6 @@ export const HomeNavigator: React.FC = () => {
<Text variant='bodyMedium'>{t('drawers.relaysDescription')}</Text> <Text variant='bodyMedium'>{t('drawers.relaysDescription')}</Text>
</View> </View>
</RBSheet> </RBSheet>
<RBSheet
ref={bottomSheetShareRef}
closeOnDragDown={true}
customStyles={bottomSheetStyles}
onClose={() => setDisplayUserShareDrawer(undefined)}
>
<ProfileShare />
</RBSheet>
</> </>
) )
} }

View File

@ -60,7 +60,6 @@ export const ProfileCreatePage: React.FC<ProfileCreatePageProps> = ({ navigation
const onPress: () => void = () => { const onPress: () => void = () => {
if (step > 1) { if (step > 1) {
console.log(validConfirmation())
if (validConfirmation()) { if (validConfirmation()) {
setPrivateKey(key) setPrivateKey(key)
setUserState('ready') setUserState('ready')

View File

@ -1,5 +1,5 @@
import React, { useContext, useEffect, useState } from 'react' import React, { useContext, useEffect, useState } from 'react'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext' import { RelayPoolContext, WebsocketEvent } from '../../Contexts/RelayPoolContext'
import { Kind } from 'nostr-tools' import { Kind } from 'nostr-tools'
import { AppContext } from '../../Contexts/AppContext' import { AppContext } from '../../Contexts/AppContext'
import { UserContext } from '../../Contexts/UserContext' import { UserContext } from '../../Contexts/UserContext'
@ -7,28 +7,32 @@ import { getUsers, User } from '../../Functions/DatabaseFunctions/Users'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import getUnixTime from 'date-fns/getUnixTime' import getUnixTime from 'date-fns/getUnixTime'
import debounce from 'lodash.debounce' import debounce from 'lodash.debounce'
import { StyleSheet, View } from 'react-native' import { DeviceEventEmitter, StyleSheet, View } from 'react-native'
import Logo from '../../Components/Logo' import Logo from '../../Components/Logo'
import { Button, Text, useTheme } from 'react-native-paper' import { Button, Text, useTheme } from 'react-native-paper'
import { navigate } from '../../lib/Navigation' import { navigate } from '../../lib/Navigation'
import { useFocusEffect } from '@react-navigation/native' import { useFocusEffect } from '@react-navigation/native'
import { formatId } from '../../Functions/RelayFunctions/Users'
export const ProfileLoadPage: React.FC = () => { export const ProfileLoadPage: React.FC = () => {
const theme = useTheme() const theme = useTheme()
const { database } = useContext(AppContext) const { database } = useContext(AppContext)
const { relayPool, lastEventId, relayPoolReady } = useContext(RelayPoolContext) const { relayPool, relayPoolReady } = useContext(RelayPoolContext)
const { publicKey, reloadUser, setUserState, name } = useContext(UserContext) const { publicKey, reloadUser, setUserState, name } = useContext(UserContext)
const { t } = useTranslation('common') const { t } = useTranslation('common')
const [profileFound, setProfileFound] = useState<boolean>(false) const [profileFound, setProfileFound] = useState<boolean>(false)
const [contactsCount, setContactsCount] = useState<number>(0) const [contactsCount, setContactsCount] = useState<number>(0)
const [lastEventId, setLastEventId] = useState<string>('')
useFocusEffect( useFocusEffect(
React.useCallback(() => { React.useCallback(() => {
DeviceEventEmitter.addListener('WebsocketEvent', (event: WebsocketEvent) =>
setLastEventId(event.eventId),
)
debounce(() => { debounce(() => {
loadMeta() loadMeta()
loadPets() reloadUser()
}, 1000) }, 1000)
return () => return () =>
relayPool?.unsubscribe([ relayPool?.unsubscribe([
'profile-load-meta', 'profile-load-meta',
@ -39,14 +43,16 @@ export const ProfileLoadPage: React.FC = () => {
) )
useEffect(() => { useEffect(() => {
loadMeta() if (!name) reloadUser()
loadPets() }, [lastEventId, publicKey, relayPoolReady])
reloadUser()
if (name) { useEffect(() => {
setProfileFound(true) if (name) setProfileFound(true)
loadPets() }, [name])
}
}, [lastEventId, name]) useEffect(() => {
if (profileFound) loadPets()
}, [profileFound, publicKey, relayPoolReady])
useEffect(() => { useEffect(() => {
if (publicKey && relayPoolReady) loadMeta() if (publicKey && relayPoolReady) loadMeta()
@ -56,23 +62,17 @@ export const ProfileLoadPage: React.FC = () => {
if (publicKey && relayPoolReady) { if (publicKey && relayPoolReady) {
relayPool?.subscribe('profile-load-meta', [ relayPool?.subscribe('profile-load-meta', [
{ {
kinds: [Kind.Text, Kind.Contacts, Kind.Metadata], kinds: [Kind.Contacts, Kind.Metadata],
authors: [publicKey], authors: [publicKey],
}, },
]) ])
relayPool?.subscribe('profile-load-meta-pets', [
{
kinds: [Kind.Contacts],
'#p': [publicKey],
},
])
} }
} }
const loadPets: () => void = () => { const loadPets: () => void = () => {
if (database && publicKey && relayPoolReady) { if (database && publicKey && relayPoolReady) {
getUsers(database, {}).then((results) => { getUsers(database, {}).then((results) => {
if (results && results.length > 0) { if (results.length > 0) {
setContactsCount(results.filter((user) => user.contact).length) setContactsCount(results.filter((user) => user.contact).length)
const authors = [...results.map((user: User) => user.id), publicKey] const authors = [...results.map((user: User) => user.id), publicKey]
relayPool?.subscribe('profile-load-notes', [ relayPool?.subscribe('profile-load-notes', [
@ -103,6 +103,9 @@ export const ProfileLoadPage: React.FC = () => {
<Text variant='titleMedium' style={styles.center}> <Text variant='titleMedium' style={styles.center}>
{t('profileLoadPage.foundContacts', { contactsCount })} {t('profileLoadPage.foundContacts', { contactsCount })}
</Text> </Text>
<Text variant='titleMedium' style={styles.center}>
{t('profileLoadPage.storing', { lastEventId: formatId(lastEventId) })}
</Text>
<Button mode='contained' onPress={() => setUserState('ready')}> <Button mode='contained' onPress={() => setUserState('ready')}>
{t('profileLoadPage.home')} {t('profileLoadPage.home')}
</Button> </Button>
@ -114,6 +117,7 @@ export const ProfileLoadPage: React.FC = () => {
style={styles.warningAction} style={styles.warningAction}
mode='text' mode='text'
onPress={() => { onPress={() => {
reloadUser()
navigate('Relays') navigate('Relays')
}} }}
> >

View File

@ -7,30 +7,28 @@ import {
StyleSheet, StyleSheet,
View, View,
} from 'react-native' } from 'react-native'
import { Surface, Text, IconButton, ActivityIndicator, Snackbar } from 'react-native-paper' import { Surface, Text, ActivityIndicator, Snackbar, Divider } from 'react-native-paper'
import { FlashList, ListRenderItem } from '@shopify/flash-list' import { FlashList, ListRenderItem } from '@shopify/flash-list'
import { AppContext } from '../../Contexts/AppContext' import { AppContext } from '../../Contexts/AppContext'
import { UserContext } from '../../Contexts/UserContext' import { UserContext } from '../../Contexts/UserContext'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext' import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
import { getNotes, Note } from '../../Functions/DatabaseFunctions/Notes' import { getNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
import { getUser, updateUserContact, User } from '../../Functions/DatabaseFunctions/Users' import { getUser, User } from '../../Functions/DatabaseFunctions/Users'
import { Kind } from 'nostr-tools' import { Kind } from 'nostr-tools'
import { populatePets, username } from '../../Functions/RelayFunctions/Users'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { RelayFilters } from '../../lib/nostr/RelayPool/intex' import { RelayFilters } from '../../lib/nostr/RelayPool/intex'
import NoteCard from '../../Components/NoteCard' import NoteCard from '../../Components/NoteCard'
import LnPayment from '../../Components/LnPayment'
import { handleInfinityScroll } from '../../Functions/NativeFunctions' import { handleInfinityScroll } from '../../Functions/NativeFunctions'
import { navigate } from '../../lib/Navigation'
import { useFocusEffect } from '@react-navigation/native' import { useFocusEffect } from '@react-navigation/native'
import ProfileData from '../../Components/ProfileData' import ProfileData from '../../Components/ProfileData'
import ProfileActions from '../../Components/ProfileActions'
interface ProfilePageProps { interface ProfilePageProps {
route: { params: { pubKey: string } } route: { params: { pubKey: string } }
} }
export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => { export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
const { database, setDisplayUserShareDrawer } = useContext(AppContext) const { database } = useContext(AppContext)
const { publicKey } = useContext(UserContext) const { publicKey } = useContext(UserContext)
const { lastEventId, relayPool } = useContext(RelayPoolContext) const { lastEventId, relayPool } = useContext(RelayPoolContext)
const { t } = useTranslation('common') const { t } = useTranslation('common')
@ -38,9 +36,7 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
const [showNotification, setShowNotification] = useState<undefined | string>() const [showNotification, setShowNotification] = useState<undefined | string>()
const [notes, setNotes] = useState<Note[]>() const [notes, setNotes] = useState<Note[]>()
const [user, setUser] = useState<User>() const [user, setUser] = useState<User>()
const [openLn, setOpenLn] = React.useState<boolean>(false)
const [pageSize, setPageSize] = useState<number>(initialPageSize) const [pageSize, setPageSize] = useState<number>(initialPageSize)
const [isContact, setIsContact] = useState<boolean>()
const [refreshing, setRefreshing] = useState(false) const [refreshing, setRefreshing] = useState(false)
const [firstLoad, setFirstLoad] = useState(true) const [firstLoad, setFirstLoad] = useState(true)
@ -84,7 +80,6 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
getUser(route.params.pubKey, database).then((result) => { getUser(route.params.pubKey, database).then((result) => {
if (result) { if (result) {
setUser(result) setUser(result)
setIsContact(result?.contact)
} else if (route.params.pubKey === publicKey) { } else if (route.params.pubKey === publicKey) {
setUser({ setUser({
id: publicKey, id: publicKey,
@ -133,26 +128,6 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
relayPool?.subscribe(`profile${route.params.pubKey}`, [message]) relayPool?.subscribe(`profile${route.params.pubKey}`, [message])
} }
const removeContact: () => void = () => {
if (relayPool && database && publicKey) {
updateUserContact(route.params.pubKey, database, false).then(() => {
populatePets(relayPool, database, publicKey)
setIsContact(false)
setShowNotification('contactRemoved')
})
}
}
const addContact: () => void = () => {
if (relayPool && database && publicKey) {
updateUserContact(route.params.pubKey, database, true).then(() => {
populatePets(relayPool, database, publicKey)
setIsContact(true)
setShowNotification('contactAdded')
})
}
}
const onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void = (event) => { const onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void = (event) => {
if (handleInfinityScroll(event)) { if (handleInfinityScroll(event)) {
setPageSize(pageSize + initialPageSize) setPageSize(pageSize + initialPageSize)
@ -174,70 +149,24 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />} refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
> >
<Surface style={styles.container} elevation={1}> <Surface style={styles.container} elevation={1}>
<ProfileData <View style={styles.profileData}>
username={user?.name} <ProfileData
publicKey={route.params.pubKey} username={user?.name}
validNip05={user?.valid_nip05} publicKey={route.params.pubKey}
nip05={user?.nip05} validNip05={user?.valid_nip05}
lud06={user?.lnurl} nip05={user?.nip05}
picture={user?.picture} lud06={user?.lnurl}
avatarSize={56} picture={user?.picture}
/> avatarSize={56}
/>
<Text>{user?.follower && user.follower > 0 ? t('profilePage.isFollower') : ''}</Text>
</View>
<View> <View>
<Text>{user?.about}</Text> <Text>{user?.about}</Text>
</View> </View>
<View style={styles.mainLayout}> <Divider style={styles.divider} />
{route.params.pubKey !== publicKey && ( <View style={styles.profileActions}>
<View style={styles.actionButton}> {user && <ProfileActions user={user} setUser={setUser} />}
<IconButton
icon={
isContact ? 'account-multiple-remove-outline' : 'account-multiple-plus-outline'
}
size={28}
onPress={() => {
isContact ? removeContact() : addContact()
}}
disabled={route.params.pubKey === publicKey}
/>
<Text>{isContact ? t('profilePage.unfollow') : t('profilePage.follow')}</Text>
</View>
)}
<View style={styles.actionButton}>
<IconButton
icon='message-plus-outline'
size={28}
onPress={() => {
navigate('Conversation', {
pubKey: route.params.pubKey,
title: user ? username(user) : route.params.pubKey,
})
}}
/>
<Text>{t('profilePage.message')}</Text>
</View>
<View style={styles.actionButton}>
<IconButton
icon='share-variant-outline'
size={28}
onPress={() => {
setDisplayUserShareDrawer(user?.id)
}}
/>
<Text>{t('profileCard.share')}</Text>
</View>
<View style={styles.actionButton}>
{user?.lnurl && (
<>
<IconButton
icon='lightning-bolt'
size={28}
onPress={() => setOpenLn(true)}
iconColor='#F5D112'
/>
<Text>{t('profilePage.invoice')}</Text>
</>
)}
</View>
</View> </View>
</Surface> </Surface>
<View style={styles.list}> <View style={styles.list}>
@ -265,7 +194,6 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
{t(`profilePage.${showNotification}`)} {t(`profilePage.${showNotification}`)}
</Snackbar> </Snackbar>
)} )}
<LnPayment setOpen={setOpenLn} open={openLn} user={user} />
</View> </View>
) )
} }
@ -274,26 +202,6 @@ const styles = StyleSheet.create({
container: { container: {
padding: 16, padding: 16,
}, },
contacts: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
},
mainLayout: {
flexDirection: 'row',
alignItems: 'center',
},
userData: {
paddingLeft: 16,
},
userName: {
flexDirection: 'row',
},
actionButton: {
justifyContent: 'center',
alignItems: 'center',
width: 100,
},
snackbar: { snackbar: {
margin: 16, margin: 16,
bottom: 70, bottom: 70,
@ -301,12 +209,19 @@ const styles = StyleSheet.create({
list: { list: {
padding: 16, padding: 16,
}, },
divider: {
marginTop: 16,
},
profileActions: {
marginRight: 16,
marginLeft: 16,
},
noteCard: { noteCard: {
marginBottom: 16, marginBottom: 16,
}, },
verifyIcon: { profileData: {
paddingTop: 6, flexDirection: 'row',
paddingLeft: 5, justifyContent: 'space-between',
}, },
}) })

View File

@ -3,7 +3,7 @@ import { FlatList, ListRenderItem, ScrollView, StyleSheet, View } from 'react-na
import Clipboard from '@react-native-clipboard/clipboard' import Clipboard from '@react-native-clipboard/clipboard'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext' import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
import { Relay } from '../../Functions/DatabaseFunctions/Relays' import { getRelays, Relay } from '../../Functions/DatabaseFunctions/Relays'
import { REGEX_SOCKET_LINK } from '../../Constants/Relay' import { REGEX_SOCKET_LINK } from '../../Constants/Relay'
import { import {
List, List,
@ -21,6 +21,7 @@ import RBSheet from 'react-native-raw-bottom-sheet'
import { relayToColor } from '../../Functions/NativeFunctions' import { relayToColor } from '../../Functions/NativeFunctions'
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons' import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
import { useFocusEffect } from '@react-navigation/native' import { useFocusEffect } from '@react-navigation/native'
import { AppContext } from '../../Contexts/AppContext'
export const defaultRelays = [ export const defaultRelays = [
'wss://brb.io', 'wss://brb.io',
@ -39,13 +40,14 @@ export const defaultRelays = [
export const RelaysPage: React.FC = () => { export const RelaysPage: React.FC = () => {
const defaultRelayInput = React.useMemo(() => 'wss://', []) const defaultRelayInput = React.useMemo(() => 'wss://', [])
const { updateRelayItem, addRelayItem, removeRelayItem, relays, relayPool } = const { updateRelayItem, addRelayItem, removeRelayItem, relayPool } = useContext(RelayPoolContext)
useContext(RelayPoolContext) const { database } = useContext(AppContext)
const { t } = useTranslation('common') const { t } = useTranslation('common')
const theme = useTheme() const theme = useTheme()
const bottomSheetAddRef = React.useRef<RBSheet>(null) const bottomSheetAddRef = React.useRef<RBSheet>(null)
const bottomSheetEditRef = React.useRef<RBSheet>(null) const bottomSheetEditRef = React.useRef<RBSheet>(null)
const bottomSheetResilenseRef = React.useRef<RBSheet>(null) const bottomSheetResilenseRef = React.useRef<RBSheet>(null)
const [relays, setRelays] = React.useState<Relay[]>([])
const [selectedRelay, setSelectedRelay] = useState<Relay>() const [selectedRelay, setSelectedRelay] = useState<Relay>()
const [addRelayInput, setAddRelayInput] = useState<string>(defaultRelayInput) const [addRelayInput, setAddRelayInput] = useState<string>(defaultRelayInput)
const [showNotification, setShowNotification] = useState<string>() const [showNotification, setShowNotification] = useState<string>()
@ -53,17 +55,23 @@ export const RelaysPage: React.FC = () => {
useFocusEffect( useFocusEffect(
React.useCallback(() => { React.useCallback(() => {
relayPool?.unsubscribeAll() relayPool?.unsubscribeAll()
updateRelays()
return () => {} return () => {}
}, []), }, []),
) )
const updateRelays: () => void = () => {
if (database) getRelays(database).then(setRelays)
}
const addRelay: (url: string) => void = (url) => { const addRelay: (url: string) => void = (url) => {
addRelayItem({ addRelayItem({
url, url,
active: 1, active: 1,
global_feed: 1, global_feed: 1,
}).then(() => { }).then(() => {
updateRelays()
setShowNotification('add') setShowNotification('add')
}) })
} }
@ -72,6 +80,7 @@ export const RelaysPage: React.FC = () => {
removeRelayItem({ removeRelayItem({
url, url,
}).then(() => { }).then(() => {
updateRelays()
setShowNotification('remove') setShowNotification('remove')
}) })
} }
@ -79,6 +88,7 @@ export const RelaysPage: React.FC = () => {
const activeRelay: (relay: Relay) => void = (relay) => { const activeRelay: (relay: Relay) => void = (relay) => {
relay.active = 1 relay.active = 1
updateRelayItem(relay).then(() => { updateRelayItem(relay).then(() => {
updateRelays()
setShowNotification('active') setShowNotification('active')
}) })
} }
@ -87,6 +97,7 @@ export const RelaysPage: React.FC = () => {
relay.active = 0 relay.active = 0
relay.global_feed = 0 relay.global_feed = 0
updateRelayItem(relay).then(() => { updateRelayItem(relay).then(() => {
updateRelays()
setShowNotification('desactive') setShowNotification('desactive')
}) })
} }
@ -95,6 +106,7 @@ export const RelaysPage: React.FC = () => {
relay.active = 1 relay.active = 1
relay.global_feed = 1 relay.global_feed = 1
updateRelayItem(relay).then(() => { updateRelayItem(relay).then(() => {
updateRelays()
setShowNotification('globalFeedActive') setShowNotification('globalFeedActive')
}) })
} }
@ -102,6 +114,7 @@ export const RelaysPage: React.FC = () => {
const desactiveGlobalFeedRelay: (relay: Relay) => void = (relay) => { const desactiveGlobalFeedRelay: (relay: Relay) => void = (relay) => {
relay.global_feed = 0 relay.global_feed = 0
updateRelayItem(relay).then(() => { updateRelayItem(relay).then(() => {
updateRelays()
setShowNotification('globalFeedActiveUnactive') setShowNotification('globalFeedActiveUnactive')
}) })
} }
@ -229,7 +242,7 @@ export const RelaysPage: React.FC = () => {
</Text> </Text>
<IconButton <IconButton
style={styles.titleAction} style={styles.titleAction}
icon='question' icon='help'
size={20} size={20}
onPress={() => bottomSheetResilenseRef.current?.open()} onPress={() => bottomSheetResilenseRef.current?.open()}
/> />

View File

@ -126,15 +126,11 @@ export const SendPage: React.FC<SendPageProps> = ({ route }) => {
let tags: string[][] = [] let tags: string[][] = []
let rawContent = content let rawContent = content
if (note?.id) { if (note?.id) {
tags = note.tags
if (route.params?.type === 'reply') { if (route.params?.type === 'reply') {
tags = note.tags const eTags = getETags(note)
if (getETags(note).length === 0) { tags.push(['e', note.id, '', eTags.length > 0 ? 'reply' : 'root'])
tags.push(['e', note.id, '', 'root'])
} else {
tags.push(['e', note.id, '', 'reply'])
}
tags.push(['p', note.pubkey, '']) tags.push(['p', note.pubkey, ''])
} else if (route.params?.type === 'repost') { } else if (route.params?.type === 'repost') {
rawContent = `#[${tags.length}] ${rawContent}` rawContent = `#[${tags.length}] ${rawContent}`
@ -149,7 +145,7 @@ export const SendPage: React.FC<SendPageProps> = ({ route }) => {
const userText = mentionText(user) const userText = mentionText(user)
if (rawContent.includes(userText)) { if (rawContent.includes(userText)) {
rawContent = rawContent.replace(userText, `#[${tags.length}]`) rawContent = rawContent.replace(userText, `#[${tags.length}]`)
tags.push(['p', user.id]) tags.push(['p', user.id, ''])
} }
}) })
} }