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])
const publishReaction: (positive: boolean) => void = (positive) => {
if (note && publicKey) {
if (note?.id && publicKey) {
const event: Event = {
content: positive ? '+' : '-',
created_at: getUnixTime(new Date()),
kind: Kind.Reaction,
pubkey: publicKey,
tags: [...(note.tags ?? []), ['e', note.id], ['p', note.pubkey]],
tags: [...note.tags, ['e', note.id], ['p', note.pubkey]],
}
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 * as React from 'react'
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 { RelayPoolContext } from '../../Contexts/RelayPoolContext'
import { UserContext } from '../../Contexts/UserContext'
import {
getUser,
updateUserBlock,
updateUserContact,
User,
} from '../../Functions/DatabaseFunctions/Users'
import { populatePets, usernamePubKey } from '../../Functions/RelayFunctions/Users'
import { getUser, User } from '../../Functions/DatabaseFunctions/Users'
import { usernamePubKey } from '../../Functions/RelayFunctions/Users'
import LnPayment from '../LnPayment'
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 { getNpub } from '../../lib/nostr/Nip19'
import ProfileData from '../ProfileData'
import ProfileActions from '../ProfileActions'
interface ProfileCardProps {
bottomSheetRef: React.RefObject<RBSheet>
@ -27,13 +21,9 @@ interface ProfileCardProps {
export const ProfileCard: React.FC<ProfileCardProps> = ({ bottomSheetRef, showImages = true }) => {
const theme = useTheme()
const { displayUserDrawer } = React.useContext(AppContext)
const { database, setDisplayUserShareDrawer } = React.useContext(AppContext)
const { publicKey } = React.useContext(UserContext)
const { relayPool } = React.useContext(RelayPoolContext)
const { database } = React.useContext(AppContext)
const [user, setUser] = React.useState<User>()
const [blocked, setBlocked] = React.useState<number>()
const [openLn, setOpenLn] = React.useState<boolean>(false)
const [isContact, setIsContact] = React.useState<boolean>()
const [showNotification, setShowNotification] = React.useState<undefined | string>()
const nPub = React.useMemo(() => getNpub(displayUserDrawer), [displayUserDrawer])
const username = React.useMemo(() => usernamePubKey(user?.name ?? '', nPub), [nPub, user])
@ -42,45 +32,13 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({ bottomSheetRef, showIm
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 = () => {
if (database && displayUserDrawer) {
getUser(displayUserDrawer, database).then((result) => {
if (result) {
setUser(result)
setBlocked(result.blocked)
setIsContact(result?.contact)
} else {
setUser({ id: displayUserDrawer })
setBlocked(0)
}
})
}
@ -93,9 +51,10 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({ bottomSheetRef, showIm
return (
<View>
<Card onPress={goToProfile}>
<Card.Content style={styles.card}>
<View style={styles.cardUser}>
<Divider />
<TouchableRipple onPress={goToProfile}>
<View style={styles.cardUser}>
<View>
<View style={styles.cardUserMain}>
<ProfileData
username={user?.name}
@ -107,81 +66,18 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({ bottomSheetRef, showIm
avatarSize={54}
/>
</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 style={styles.arrow}>
<MaterialCommunityIcons
name='menu-right'
size={25}
color={theme.colors.onPrimaryContainer}
/>
</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 style={styles.actionButton}>
<IconButton
icon='share-variant-outline'
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>
</TouchableRipple>
<Divider />
{user && <ProfileActions user={user} setUser={setUser} />}
{showNotification && (
<Snackbar
style={styles.snackbar}
@ -229,6 +125,11 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
},
cardUser: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingTop: 16,
},
card: {
flexDirection: 'row',
alignItems: 'center',
@ -258,13 +159,18 @@ const styles = StyleSheet.create({
list: {
padding: 16,
},
cardUser: {
flex: 1,
},
verifyIcon: {
paddingTop: 6,
paddingLeft: 5,
},
dividerTop: {
marginBottom: 16,
},
arrow: {
alignContent: 'center',
justifyContent: 'center',
marginTop: -16,
},
})
export default ProfileCard

View File

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

View File

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

View File

@ -1,10 +1,11 @@
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 { DeviceEventEmitter } from 'react-native'
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 { randomInt } from '../Functions/NativeFunctions'
export interface RelayPoolContextProps {
relayPoolReady: boolean
@ -69,15 +70,25 @@ export const RelayPoolContextProvider = ({
[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 () => {
if (database && publicKey) {
DeviceEventEmitter.addListener('WebsocketEvent', debouncedEventIdHandler)
DeviceEventEmitter.addListener('WebsocketConfirmation', debouncedConfirmationHandler)
const initRelayPool = new RelayPool(privateKey)
await initRelayPool.resilientMode(database, publicKey)
initRelayPool.connect(publicKey, () => setRelayPoolReady(true))
setRelayPool(initRelayPool)
loadRelays()
initRelayPool.connect(publicKey, () => {
initRelayPool.resilientMode(database, publicKey)
setRelayPool(initRelayPool)
})
}
}
@ -137,10 +148,23 @@ export const RelayPoolContextProvider = ({
useEffect(() => {
if (publicKey && publicKey !== '') {
DeviceEventEmitter.addListener('WebsocketEvent', debouncedEventIdHandler)
DeviceEventEmitter.addListener('WebsocketConfirmation', debouncedConfirmationHandler)
loadRelayPool()
}
}, [publicKey])
useEffect(() => {
if (database && relayPool) {
getActiveRelays(database).then((results) => {
if (results.length < 1) {
createRandomRelays()
}
loadRelays().then(() => setRelayPoolReady(true))
})
}
}, [relayPool])
return (
<RelayPoolContext.Provider
value={{

View File

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

View File

@ -1,10 +1,34 @@
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
pubkey: string
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: (
db: QuickSQLiteConnection,

View File

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

View File

@ -8,7 +8,7 @@ export interface User {
picture?: string
about?: string
contact?: boolean
follower?: boolean
follower?: number
lnurl?: string
nip05?: string
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) => {
if (url) {
const regexp = /^(https?:\/\/.*\.(?:png|jpg|jpeg|gif|webp))$/
const regexp = /^(https?:\/\/.*\.(?:png|jpg|jpeg|gif|webp)(\?.*)?)$/
return regexp.test(url)
} else {
return false

View File

@ -38,7 +38,13 @@ export const formatPubKey: (pubKey: string | undefined) => string = (pubKey) =>
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) => {

View File

@ -41,7 +41,8 @@
"home": "Start",
"searchingProfile": "Durchsuche Profil",
"foundProfile": "Profil gefunden",
"foundContacts": "{{contactsCount}} Kontakte gefunden"
"foundContacts": "{{contactsCount}} Kontakte gefunden",
"storing": "Storing {{lastEventId}} on database."
},
"sendPage": {
"isContact": "Folge ich",
@ -257,13 +258,18 @@
"notifications": {
"contactAdded": "Kontakt hinzugefügt",
"contactRemoved": "Entfernt ",
"npubCopied": "Öffentlicher Schlüssel kopiert."
"npubCopied": "Öffentlicher Schlüssel kopiert.",
"active": "Relay aktiviert.",
"desactive": "Relay deaktiviert."
},
"invoice": "Zap",
"message": "Nachricht",
"follow": "Folgen",
"isFollower": "follows you",
"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": {
"clipboardTitle": "Nostr Schlüssel entdeckt",

View File

@ -40,8 +40,9 @@
"relays": "See relays",
"home": "Go Home",
"searchingProfile": "Searching for your profile",
"foundProfile": "Found profile",
"foundContacts": "{{contactsCount}} contacts found"
"foundProfile": "Profile found",
"foundContacts": "{{contactsCount}} contacts found",
"storing": "Storing {{lastEventId}} on database."
},
"sendPage": {
"isContact": "Following",
@ -175,9 +176,9 @@
},
"relaysPage": {
"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",
"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",
"small": "Small",
"contacts": "# Contacts",
@ -269,13 +270,18 @@
"notifications": {
"contactAdded": "Profile followed",
"contactRemoved": "Profile unfollowed",
"npubCopied": "Public key copied."
"npubCopied": "Public key copied.",
"active": "Relay activated.",
"desactive": "Relay desactivated."
},
"invoice": "Tip",
"message": "Message",
"follow": "Follow",
"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": {
"clipboardTitle": "Nostr key detected",

View File

@ -41,7 +41,8 @@
"home": "Acceder",
"searchingProfile": "Buscando tu perfil",
"foundProfile": "Perfil encontrado",
"foundContacts": "{{contactsCount}} contactos encontrados"
"foundContacts": "{{contactsCount}} contactos encontrados",
"storing": "Guardando {{lastEventId}} en base de datos."
},
"sendPage": {
"isContact": "Siguiendo",
@ -251,13 +252,18 @@
"notifications": {
"contactAdded": "Siguiendo",
"contactRemoved": "Dejando de seguir ",
"npubCopied": "Clave pública copiada."
"npubCopied": "Clave pública copiada.",
"active": "Relay activado.",
"desactive": "Relay desactivado."
},
"invoice": "Propina",
"message": "Mensaje",
"isFollower": "te sigue",
"follow": "Seguir",
"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": {
"clipboardTitle": "Se ha detectado una clave Nostr",

View File

@ -41,7 +41,8 @@
"home": "Accès",
"searchingProfile": "A la recherche de votre profil",
"foundProfile": "Profil trouvé",
"foundContacts": "{{contactsCount}} contacts trouvés"
"foundContacts": "{{contactsCount}} contacts trouvés",
"storing": "Storing {{lastEventId}} on database."
},
"sendPage": {
"isContact": "Abonné",
@ -252,13 +253,18 @@
"notifications": {
"contactAdded": "Abonné",
"contactRemoved": "Vous ne suivez plus ce profil",
"npubCopied": "Clé publique copiée"
"npubCopied": "Clé publique copiée",
"active": "Actif",
"share": "Partager"
},
"invoice": "Facture",
"message": "Message",
"follow": "Suivre",
"isFollower": "follows you",
"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": {
"notifications": {

View File

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

View File

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

View File

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

View File

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

View File

@ -7,30 +7,28 @@ import {
StyleSheet,
View,
} 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 { AppContext } from '../../Contexts/AppContext'
import { UserContext } from '../../Contexts/UserContext'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
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 { populatePets, username } from '../../Functions/RelayFunctions/Users'
import { useTranslation } from 'react-i18next'
import { RelayFilters } from '../../lib/nostr/RelayPool/intex'
import NoteCard from '../../Components/NoteCard'
import LnPayment from '../../Components/LnPayment'
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
import { navigate } from '../../lib/Navigation'
import { useFocusEffect } from '@react-navigation/native'
import ProfileData from '../../Components/ProfileData'
import ProfileActions from '../../Components/ProfileActions'
interface ProfilePageProps {
route: { params: { pubKey: string } }
}
export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
const { database, setDisplayUserShareDrawer } = useContext(AppContext)
const { database } = useContext(AppContext)
const { publicKey } = useContext(UserContext)
const { lastEventId, relayPool } = useContext(RelayPoolContext)
const { t } = useTranslation('common')
@ -38,9 +36,7 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
const [showNotification, setShowNotification] = useState<undefined | string>()
const [notes, setNotes] = useState<Note[]>()
const [user, setUser] = useState<User>()
const [openLn, setOpenLn] = React.useState<boolean>(false)
const [pageSize, setPageSize] = useState<number>(initialPageSize)
const [isContact, setIsContact] = useState<boolean>()
const [refreshing, setRefreshing] = useState(false)
const [firstLoad, setFirstLoad] = useState(true)
@ -84,7 +80,6 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
getUser(route.params.pubKey, database).then((result) => {
if (result) {
setUser(result)
setIsContact(result?.contact)
} else if (route.params.pubKey === publicKey) {
setUser({
id: publicKey,
@ -133,26 +128,6 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
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) => {
if (handleInfinityScroll(event)) {
setPageSize(pageSize + initialPageSize)
@ -174,70 +149,24 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
>
<Surface style={styles.container} elevation={1}>
<ProfileData
username={user?.name}
publicKey={route.params.pubKey}
validNip05={user?.valid_nip05}
nip05={user?.nip05}
lud06={user?.lnurl}
picture={user?.picture}
avatarSize={56}
/>
<View style={styles.profileData}>
<ProfileData
username={user?.name}
publicKey={route.params.pubKey}
validNip05={user?.valid_nip05}
nip05={user?.nip05}
lud06={user?.lnurl}
picture={user?.picture}
avatarSize={56}
/>
<Text>{user?.follower && user.follower > 0 ? t('profilePage.isFollower') : ''}</Text>
</View>
<View>
<Text>{user?.about}</Text>
</View>
<View style={styles.mainLayout}>
{route.params.pubKey !== publicKey && (
<View style={styles.actionButton}>
<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>
<Divider style={styles.divider} />
<View style={styles.profileActions}>
{user && <ProfileActions user={user} setUser={setUser} />}
</View>
</Surface>
<View style={styles.list}>
@ -265,7 +194,6 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
{t(`profilePage.${showNotification}`)}
</Snackbar>
)}
<LnPayment setOpen={setOpenLn} open={openLn} user={user} />
</View>
)
}
@ -274,26 +202,6 @@ const styles = StyleSheet.create({
container: {
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: {
margin: 16,
bottom: 70,
@ -301,12 +209,19 @@ const styles = StyleSheet.create({
list: {
padding: 16,
},
divider: {
marginTop: 16,
},
profileActions: {
marginRight: 16,
marginLeft: 16,
},
noteCard: {
marginBottom: 16,
},
verifyIcon: {
paddingTop: 6,
paddingLeft: 5,
profileData: {
flexDirection: 'row',
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 { useTranslation } from 'react-i18next'
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 {
List,
@ -21,6 +21,7 @@ import RBSheet from 'react-native-raw-bottom-sheet'
import { relayToColor } from '../../Functions/NativeFunctions'
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
import { useFocusEffect } from '@react-navigation/native'
import { AppContext } from '../../Contexts/AppContext'
export const defaultRelays = [
'wss://brb.io',
@ -39,13 +40,14 @@ export const defaultRelays = [
export const RelaysPage: React.FC = () => {
const defaultRelayInput = React.useMemo(() => 'wss://', [])
const { updateRelayItem, addRelayItem, removeRelayItem, relays, relayPool } =
useContext(RelayPoolContext)
const { updateRelayItem, addRelayItem, removeRelayItem, relayPool } = useContext(RelayPoolContext)
const { database } = useContext(AppContext)
const { t } = useTranslation('common')
const theme = useTheme()
const bottomSheetAddRef = React.useRef<RBSheet>(null)
const bottomSheetEditRef = React.useRef<RBSheet>(null)
const bottomSheetResilenseRef = React.useRef<RBSheet>(null)
const [relays, setRelays] = React.useState<Relay[]>([])
const [selectedRelay, setSelectedRelay] = useState<Relay>()
const [addRelayInput, setAddRelayInput] = useState<string>(defaultRelayInput)
const [showNotification, setShowNotification] = useState<string>()
@ -53,17 +55,23 @@ export const RelaysPage: React.FC = () => {
useFocusEffect(
React.useCallback(() => {
relayPool?.unsubscribeAll()
updateRelays()
return () => {}
}, []),
)
const updateRelays: () => void = () => {
if (database) getRelays(database).then(setRelays)
}
const addRelay: (url: string) => void = (url) => {
addRelayItem({
url,
active: 1,
global_feed: 1,
}).then(() => {
updateRelays()
setShowNotification('add')
})
}
@ -72,6 +80,7 @@ export const RelaysPage: React.FC = () => {
removeRelayItem({
url,
}).then(() => {
updateRelays()
setShowNotification('remove')
})
}
@ -79,6 +88,7 @@ export const RelaysPage: React.FC = () => {
const activeRelay: (relay: Relay) => void = (relay) => {
relay.active = 1
updateRelayItem(relay).then(() => {
updateRelays()
setShowNotification('active')
})
}
@ -87,6 +97,7 @@ export const RelaysPage: React.FC = () => {
relay.active = 0
relay.global_feed = 0
updateRelayItem(relay).then(() => {
updateRelays()
setShowNotification('desactive')
})
}
@ -95,6 +106,7 @@ export const RelaysPage: React.FC = () => {
relay.active = 1
relay.global_feed = 1
updateRelayItem(relay).then(() => {
updateRelays()
setShowNotification('globalFeedActive')
})
}
@ -102,6 +114,7 @@ export const RelaysPage: React.FC = () => {
const desactiveGlobalFeedRelay: (relay: Relay) => void = (relay) => {
relay.global_feed = 0
updateRelayItem(relay).then(() => {
updateRelays()
setShowNotification('globalFeedActiveUnactive')
})
}
@ -229,7 +242,7 @@ export const RelaysPage: React.FC = () => {
</Text>
<IconButton
style={styles.titleAction}
icon='question'
icon='help'
size={20}
onPress={() => bottomSheetResilenseRef.current?.open()}
/>

View File

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