From 8199f4a5270c3667bd28337848140a15d9ef4de3 Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Sat, 14 Jan 2023 15:52:10 +0100 Subject: [PATCH 1/2] New UI navigation bar --- frontend/Components/Avatar/index.tsx | 70 +++---- frontend/Components/MenuItems/index.tsx | 198 ++++++++++++------ frontend/Contexts/AppContext.tsx | 45 +--- frontend/Contexts/RelayPoolContext.tsx | 56 +++-- .../DatabaseFunctions/Reactions/index.ts | 2 +- .../DatabaseFunctions/Relays/index.ts | 1 + .../DatabaseFunctions/Users/index.ts | 16 ++ frontend/Locales/en.json | 9 +- frontend/Pages/FeedNavigator/index.tsx | 19 +- frontend/Pages/HomeNavigator/index.tsx | 2 +- frontend/Pages/HomePage/index.tsx | 2 +- .../index.tsx | 32 +-- frontend/Pages/ProfileConnectPage/index.tsx | 12 +- frontend/Pages/ProfileLoadPage/index.tsx | 17 +- frontend/Pages/RelaysPage/index.tsx | 57 ++--- frontend/index.tsx | 24 +-- frontend/lib/Navigation/index.ts | 9 + 17 files changed, 300 insertions(+), 271 deletions(-) rename frontend/Pages/{ConfigPage => ProfileConfigPage}/index.tsx (88%) create mode 100644 frontend/lib/Navigation/index.ts diff --git a/frontend/Components/Avatar/index.tsx b/frontend/Components/Avatar/index.tsx index b6a6ac8..6056740 100644 --- a/frontend/Components/Avatar/index.tsx +++ b/frontend/Components/Avatar/index.tsx @@ -1,48 +1,19 @@ import React from 'react' -import { Layout, useTheme } from '@ui-kitten/components' -import { Image, StyleSheet, Text } from 'react-native' -import { stringToColour } from '../../Functions/NativeFunctions' +import { StyleSheet } from 'react-native' +import { Avatar as PaperAvatar, useTheme } from 'react-native-paper' interface AvatarProps { + pubKey: string src?: string name?: string - pubKey: string size?: number + lud06?: string } -export const Avatar: React.FC = ({ src, name, pubKey, size = 50 }) => { +export const NostrosAvatar: React.FC = ({ src, name, pubKey, size = 40, lud06 }) => { const theme = useTheme() const displayName = name && name !== '' ? name : pubKey - const styles = StyleSheet.create({ - layout: { - flexDirection: 'row', - alignContent: 'center', - width: size, - height: size, - backgroundColor: 'transparent', - }, - image: { - width: size, - height: size, - borderRadius: 100, - }, - textAvatarLayout: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - width: size, - height: size, - borderRadius: 100, - backgroundColor: stringToColour(pubKey), - }, - textAvatar: { - fontSize: size / 2, - alignContent: 'center', - color: theme['text-basic-color'], - textTransform: 'uppercase', - }, - }) - + const hasLud06 = lud06 && lud06 !== '' const validImage: () => boolean = () => { if (src) { const regexp = /^(https?:\/\/.*\.(?:png|jpg|jpeg))$/ @@ -53,16 +24,31 @@ export const Avatar: React.FC = ({ src, name, pubKey, size = 50 }) } return ( - + <> {validImage() ? ( - + ) : ( - - {displayName.substring(0, 2)} - + )} - + {hasLud06 && ( + + )} + ) } -export default Avatar +const styles = StyleSheet.create({ + iconLightning: { + top: -14, + }, +}) + +export default NostrosAvatar diff --git a/frontend/Components/MenuItems/index.tsx b/frontend/Components/MenuItems/index.tsx index bc8e7f0..163f4db 100644 --- a/frontend/Components/MenuItems/index.tsx +++ b/frontend/Components/MenuItems/index.tsx @@ -1,7 +1,7 @@ import * as React from 'react' -import { StyleSheet } from 'react-native' +import { StyleSheet, View } from 'react-native' import { DrawerContentScrollView } from '@react-navigation/drawer' -import { Button, Drawer, Text, useTheme } from 'react-native-paper' +import { Button, Card, Chip, Drawer, IconButton, Text, useTheme } from 'react-native-paper' import Logo from '../Logo' import { useTranslation } from 'react-i18next' import SInfo from 'react-native-sensitive-info' @@ -9,25 +9,40 @@ import { RelayPoolContext } from '../../Contexts/RelayPoolContext' import { dropTables } from '../../Functions/DatabaseFunctions' import { AppContext } from '../../Contexts/AppContext' import { DrawerNavigationHelpers } from '@react-navigation/drawer/lib/typescript/src/types' - -interface ItemList { - label: string - icon: string - key: number - right?: () => JSX.Element -} +import { navigate } from '../../lib/Navigation' +import NostrosAvatar from '../Avatar' +import { + getContactsCount, + getFollowersCount, + getUser, + User, +} from '../../Functions/DatabaseFunctions/Users' +import { formatPubKey } from '../../Functions/RelayFunctions/Users' interface MenuItemsProps { - navigation: DrawerNavigationHelpers; + navigation: DrawerNavigationHelpers } export const MenuItems: React.FC = ({ navigation }) => { const [drawerItemIndex, setDrawerItemIndex] = React.useState(-1) - const { goToPage, database, init } = React.useContext(AppContext) - const { setPrivateKey, setPublicKey, relayPool, publicKey } = React.useContext(RelayPoolContext) + const { database, init } = React.useContext(AppContext) + const { setPrivateKey, setPublicKey, relayPool, publicKey, relays } = React.useContext(RelayPoolContext) + const [user, setUser] = React.useState() + const [contactsCount, setContantsCount] = React.useState() + const [followersCount, setFollowersCount] = React.useState() const { t } = useTranslation('common') const theme = useTheme() + React.useEffect(() => { + if (database && publicKey) { + getUser(publicKey, database).then((result) => { + if (result) setUser(result) + }) + getContactsCount(database).then(setContantsCount) + getFollowersCount(database).then(setFollowersCount) + } + }, [publicKey, database]) + const onPressLogout: () => void = () => { if (database) { relayPool?.unsubscribeAll() @@ -37,48 +52,24 @@ export const MenuItems: React.FC = ({ navigation }) => { SInfo.deleteItem('privateKey', {}).then(() => { SInfo.deleteItem('publicKey', {}).then(() => { init() - goToPage('landing', true) + navigate('Home', { screen: 'ProfileConnect' }) }) }) }) } } - const onPressItem: (index:number) => void = (index) => { + const onPressItem: (key: string, index: number) => void = (key, index) => { setDrawerItemIndex(index) - const pagesIndex = [ - 'Relays', - 'Config', - 'About' - ] - navigation.navigate(pagesIndex[index]) - } - - const relaysRightButton: () => JSX.Element = () => { - if (!relayPool || relayPool?.relays.length < 1) { - return {t('menuItems.notConnected')} + if (key === 'relays') { + navigate('Relays') + } else if (key === 'config') { + navigate('Feed', { page: 'Config' }) + } else if (key === 'about') { + navigate('About') } - return {t('menuItems.connectedRelays', { number: relayPool?.relays.length.toString()})} } - const DrawerItemsData = React.useMemo( - () => { - if (!publicKey) return [] - - const defaultList: ItemList[] = [ - { label: t('menuItems.relays'), icon: 'message-question-outline', key: 0, right: relaysRightButton}, - { label: t('menuItems.configuration'), icon: 'cog-outline', key: 1 } - ] - - return defaultList - }, - [publicKey], - ) - const DrawerBottomItemsData = React.useMemo( - () => [{ label: t('menuItems.about'), icon: 'message-question-outline', key: 2 }], - [], - ) - return ( <> = ({ navigation }) => { - - {DrawerItemsData.map((props, index) => ( + {user && ( + + + + + + + {user.name} + {formatPubKey(user.id)} + + + console.log('Pressed')} /> + + + + console.log('Pressed')} + > + {t('menuItems.following', { following: contactsCount })} + + console.log('Pressed')} + > + {t('menuItems.followers', { followers: followersCount })} + + + + )} + {publicKey && ( + onPressItem(index)} + label={t('menuItems.relays')} + icon='message-question-outline' + key='relays' + active={drawerItemIndex === 0} + onPress={() => onPressItem('relays', 0)} onTouchEnd={() => setDrawerItemIndex(-1)} - right={props.right} + right={() => + relays.length < 1 ? ( + {t('menuItems.notConnected')} + ) : ( + + {t('menuItems.connectedRelays', { number: relays.length })} + + ) + } /> - ))} - + onPressItem('config', 1)} + onTouchEnd={() => setDrawerItemIndex(-1)} + /> + + )} - {DrawerBottomItemsData.map((props, index) => ( - onPressItem(DrawerItemsData.length + index)} - onTouchEnd={() => setDrawerItemIndex(-1)} - /> - ))} + onPressItem('about', 2)} + onTouchEnd={() => setDrawerItemIndex(-1)} + /> {publicKey && ( @@ -142,12 +184,34 @@ export const MenuItems: React.FC = ({ navigation }) => { const styles = StyleSheet.create({ drawerContent: { flex: 1, - borderTopRightRadius: 28 + borderTopRightRadius: 28, + }, + cardContainer: { + margin: 12, + }, + cardActions: { + flexDirection: 'row', + justifyContent: 'space-between', + }, + cardActionsChip: { + width: '47%', + }, + cardAvatar: { + marginRight: 14, + }, + cardContent: { + width: '100%', + flexDirection: 'row', + }, + cardEdit: { + flexDirection: 'row', + justifyContent: 'flex-end', + flex: 1, }, bottomSection: { - padding: 24, marginBottom: 0, - borderBottomRightRadius: 28 + borderBottomRightRadius: 28, + padding: 24, }, }) diff --git a/frontend/Contexts/AppContext.tsx b/frontend/Contexts/AppContext.tsx index 350b3ab..784f717 100644 --- a/frontend/Contexts/AppContext.tsx +++ b/frontend/Contexts/AppContext.tsx @@ -2,13 +2,9 @@ import React, { useEffect, useState } from 'react' import { QuickSQLiteConnection } from 'react-native-quick-sqlite' import { initDatabase } from '../Functions/DatabaseFunctions' import SInfo from 'react-native-sensitive-info' -import { BackHandler } from 'react-native' +import { navigate } from '../lib/Navigation' export interface AppContextProps { - page: string - goToPage: (path: string, root?: boolean) => void - goBack: () => void - getActualPage: () => string init: () => void loadingDb: boolean database: QuickSQLiteConnection | null @@ -19,65 +15,30 @@ export interface AppContextProviderProps { } export const initialAppContext: AppContextProps = { - page: '', init: () => {}, - goToPage: () => {}, - getActualPage: () => '', - goBack: () => {}, loadingDb: true, database: null, } export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.Element => { - const [page, setPage] = useState(initialAppContext.page) const [database, setDatabase] = useState(null) const [loadingDb, setLoadingDb] = useState(initialAppContext.loadingDb) const init: () => void = () => { const db = initDatabase() setDatabase(db) - SInfo.getItem('privateKey', {}).then(() => { + SInfo.getItem('publicKey', {}).then((value) => { setLoadingDb(false) + if (value) navigate('Feed') }) } useEffect(init, []) - useEffect(() => { - BackHandler.addEventListener('hardwareBackPress', () => { - goBack() - return true - }) - }, [page]) - - const goToPage: (path: string, root?: boolean) => void = (path, root) => { - if (page !== '' && !root) { - setPage(`${page}%${path}`) - } else { - setPage(path) - } - } - - const goBack: () => void = () => { - const breadcrump = page.split('%') - if (breadcrump.length > 1) { - setPage(breadcrump.slice(0, -1).join('%')) - } - } - - const getActualPage: () => string = () => { - const breadcrump = page.split('%') - return breadcrump[breadcrump.length - 1] - } - return ( void lastEventId?: string lastConfirmationtId?: string + relays: Relay[] + addRelayItem: (relay: Relay) => Promise + removeRelayItem: (relay: Relay) => Promise } export interface WebsocketEvent { @@ -32,13 +36,16 @@ export const initialRelayPoolContext: RelayPoolContextProps = { setPublicKey: () => {}, setPrivateKey: () => {}, setRelayPool: () => {}, + addRelayItem: async () => await new Promise(() => {}), + removeRelayItem: async () => await new Promise(() => {}), + relays: [] } export const RelayPoolContextProvider = ({ children, images, }: RelayPoolContextProviderProps): JSX.Element => { - const { database, loadingDb, goToPage, page } = useContext(AppContext) + const { database, loadingDb } = useContext(AppContext) const [publicKey, setPublicKey] = useState() const [privateKey, setPrivateKey] = useState() @@ -48,7 +55,7 @@ export const RelayPoolContextProvider = ({ ) const [lastEventId, setLastEventId] = useState('') const [lastConfirmationtId, setLastConfirmationId] = useState('') - const [lastPage, setLastPage] = useState(page) + const [relays, setRelays] = React.useState([]) const changeEventIdHandler: (event: WebsocketEvent) => void = (event) => { setLastEventId(event.eventId) @@ -74,25 +81,45 @@ export const RelayPoolContextProvider = ({ initRelayPool.connect(publicKey, (eventId: string) => setLastEventId(eventId)) setRelayPool(initRelayPool) setLoadingRelayPool(false) + loadRelays() } } - useEffect(() => { - if (relayPool && lastPage !== page) { - setLastPage(page) + const loadRelays: () => void = () => { + if (database) { + getRelays(database).then((results) => setRelays(results)) } - }, [page]) + } + + + const addRelayItem: (relay: Relay) => Promise = async (relay) => { + return await new Promise((resolve, _reject) => { + if (relayPool && database && publicKey) { + relayPool.add(relay.url, () => { + setRelays((prev) => [...prev, relay]) + resolve() + }) + } + }) + } + + const removeRelayItem: (relay: Relay) => Promise = async (relay) => { + return await new Promise((resolve, _reject) => { + if (relayPool && database && publicKey) { + relayPool.remove(relay.url, () => { + setRelays((prev) => prev.filter((item) => item.url !== relay.url)) + resolve() + }) + } + }) + } useEffect(() => { if (publicKey && publicKey !== '') { SInfo.setItem('publicKey', publicKey, {}) - if (!loadingRelayPool && page !== 'landing') { - goToPage('home', true) - } else { - loadRelayPool() - } + loadRelayPool() } - }, [publicKey, loadingRelayPool]) + }, [publicKey]) useEffect(() => { if (privateKey && privateKey !== '') { @@ -112,8 +139,6 @@ export const RelayPoolContextProvider = ({ SInfo.getItem('publicKey', {}).then((publicResult) => { if (publicResult && publicResult !== '') { setPublicKey(publicResult) - } else { - goToPage('landing', true) } }) } @@ -133,6 +158,9 @@ export const RelayPoolContextProvider = ({ setPrivateKey, lastEventId, lastConfirmationtId, + relays, + addRelayItem, + removeRelayItem }} > {children} diff --git a/frontend/Functions/DatabaseFunctions/Reactions/index.ts b/frontend/Functions/DatabaseFunctions/Reactions/index.ts index 118a0ab..5d570f8 100644 --- a/frontend/Functions/DatabaseFunctions/Reactions/index.ts +++ b/frontend/Functions/DatabaseFunctions/Reactions/index.ts @@ -37,7 +37,7 @@ export const getReactionsCount: ( const resultSet = await db.execute(notesQuery) const item: { 'COUNT(*)': number } = resultSet?.rows?.item(0) - return item['COUNT(*)'] + return item['COUNT(*)'] ?? 0 } export const getUserReaction: ( diff --git a/frontend/Functions/DatabaseFunctions/Relays/index.ts b/frontend/Functions/DatabaseFunctions/Relays/index.ts index cd00be9..c0f5e61 100644 --- a/frontend/Functions/DatabaseFunctions/Relays/index.ts +++ b/frontend/Functions/DatabaseFunctions/Relays/index.ts @@ -28,3 +28,4 @@ export const getRelays: (db: QuickSQLiteConnection) => Promise = async const relays: Relay[] = items.map((object) => databaseToEntity(object)) return relays } + diff --git a/frontend/Functions/DatabaseFunctions/Users/index.ts b/frontend/Functions/DatabaseFunctions/Users/index.ts index 055dea0..30252cf 100644 --- a/frontend/Functions/DatabaseFunctions/Users/index.ts +++ b/frontend/Functions/DatabaseFunctions/Users/index.ts @@ -54,6 +54,22 @@ export const addUser: (pubKey: string, db: QuickSQLiteConnection) => Promise Promise = async (db) => { + const countQuery = 'SELECT COUNT(*) FROM nostros_users WHERE contact = 1' + const resultSet = db.execute(countQuery) + const item: { 'COUNT(*)': number } = resultSet?.rows?.item(0) + + return item['COUNT(*)'] ?? 0 +} + +export const getFollowersCount: (db: QuickSQLiteConnection) => Promise = async (db) => { + const countQuery = 'SELECT COUNT(*) FROM nostros_users WHERE follower = 1' + const resultSet = db.execute(countQuery) + const item: { 'COUNT(*)': number } = resultSet?.rows?.item(0) + + return item['COUNT(*)'] ?? 0 +} + export const getUsers: ( db: QuickSQLiteConnection, options: { diff --git a/frontend/Locales/en.json b/frontend/Locales/en.json index d3f4e52..01afbd2 100644 --- a/frontend/Locales/en.json +++ b/frontend/Locales/en.json @@ -1,6 +1,6 @@ { "common": { - "loggerPage": { + "homeNavigator": { "ProfileConnect": "", "ProfileLoad": "" }, @@ -10,7 +10,12 @@ "menuItems": { "relays": "Relays", "notConnected": "Not connected", - "connectedRelays": "{{number}} connected" + "connectedRelays": "{{number}} connected", + "following": "{{following}} following", + "followers": "{{followers}} followers", + "configuration": "Configuration", + "about": "About", + "logout": "Logout" } } } diff --git a/frontend/Pages/FeedNavigator/index.tsx b/frontend/Pages/FeedNavigator/index.tsx index e0c5a8b..eddb41a 100644 --- a/frontend/Pages/FeedNavigator/index.tsx +++ b/frontend/Pages/FeedNavigator/index.tsx @@ -6,6 +6,10 @@ import { Appbar, Snackbar, Text, useTheme } from 'react-native-paper' import RBSheet from "react-native-raw-bottom-sheet" import { useTranslation } from 'react-i18next' import HomePage from '../HomePage' +import RelaysPage from '../RelaysPage' +import AboutPage from '../AboutPage' +import ProfileConfigPage from '../ProfileConfigPage' +import { navigate } from '../../lib/Navigation' export const HomeNavigator: React.FC = () => { const theme = useTheme() @@ -20,10 +24,6 @@ export const HomeNavigator: React.FC = () => { [], ) - const onPressQuestion: () => void = () => { - bottomSheetRef.current?.open() - } - return ( <> { onPress={() => (navigation as any as DrawerNavigationProp<{}>).openDrawer()} /> ) : null} - - + + navigate('Config')} /> ) }, @@ -52,7 +52,12 @@ export const HomeNavigator: React.FC = () => { }} > - + + + + + + { return ( {leftAction()} - + onPressQuestion(route.name)} /> ) diff --git a/frontend/Pages/HomePage/index.tsx b/frontend/Pages/HomePage/index.tsx index 90a2ed0..4c32a74 100644 --- a/frontend/Pages/HomePage/index.tsx +++ b/frontend/Pages/HomePage/index.tsx @@ -11,13 +11,13 @@ import { import { AppContext } from '../../Contexts/AppContext' import { getMainNotes, Note } from '../../Functions/DatabaseFunctions/Notes' import NoteCard from '../../Components/NoteCard' -import Icon from 'react-native-vector-icons/FontAwesome5' import { RelayPoolContext } from '../../Contexts/RelayPoolContext' import { EventKind } from '../../lib/nostr/Events' import { getReplyEventId } from '../../Functions/RelayFunctions/Events' import { getUsers, User } from '../../Functions/DatabaseFunctions/Users' import { handleInfinityScroll } from '../../Functions/NativeFunctions' import { RelayFilters } from '../../lib/nostr/RelayPool/intex' +import { useTheme } from 'react-native-paper' export const HomePage: React.FC = () => { const { database, goToPage } = useContext(AppContext) diff --git a/frontend/Pages/ConfigPage/index.tsx b/frontend/Pages/ProfileConfigPage/index.tsx similarity index 88% rename from frontend/Pages/ConfigPage/index.tsx rename to frontend/Pages/ProfileConfigPage/index.tsx index 9a4ca84..1e97ad0 100644 --- a/frontend/Pages/ConfigPage/index.tsx +++ b/frontend/Pages/ProfileConfigPage/index.tsx @@ -1,6 +1,5 @@ -import { Divider, Input, Layout, TopNavigation, useTheme } from '@ui-kitten/components' import React, { useContext, useEffect, useState } from 'react' -import { Clipboard, ScrollView, StyleSheet } from 'react-native' +import { Clipboard, StyleSheet } from 'react-native' import { AppContext } from '../../Contexts/AppContext' import Icon from 'react-native-vector-icons/FontAwesome5' import { useTranslation } from 'react-i18next' @@ -10,10 +9,9 @@ import SInfo from 'react-native-sensitive-info' import { getUser } from '../../Functions/DatabaseFunctions/Users' import { EventKind } from '../../lib/nostr/Events' import moment from 'moment' -import { showMessage } from 'react-native-flash-message' -import { Button } from '../../Components' +import { useTheme } from 'react-native-paper' -export const ConfigPage: React.FC = () => { +export const ProfileConfigPage: React.FC = () => { const theme = useTheme() const { goToPage, goBack, database, init } = useContext(AppContext) const { setPrivateKey, setPublicKey, relayPool, publicKey, privateKey } = @@ -81,32 +79,14 @@ export const ConfigPage: React.FC = () => { tags: [], }) .then(() => { - showMessage({ - message: t('alerts.profilePublished'), - duration: 4000, - type: 'success', - }) setIsPublishingProfile(false) // restore sending status }) .catch((err) => { - showMessage({ - message: t('alerts.profilePublishError'), - description: err.message, - type: 'danger', - }) setIsPublishingProfile(false) // restore sending status }) } } - const renderBackAction = (): JSX.Element => ( - diff --git a/frontend/Pages/ProfileLoadPage/index.tsx b/frontend/Pages/ProfileLoadPage/index.tsx index 5a9471e..cacc96f 100644 --- a/frontend/Pages/ProfileLoadPage/index.tsx +++ b/frontend/Pages/ProfileLoadPage/index.tsx @@ -8,13 +8,9 @@ import moment from 'moment' import { StyleSheet, View } from 'react-native' import Logo from '../../Components/Logo' import { Button, Snackbar, Text } from 'react-native-paper' -import { DrawerNavigationHelpers } from '@react-navigation/drawer/lib/typescript/src/types' +import { navigate } from '../../lib/Navigation' -interface ProfileLoadPageProps { - navigation: DrawerNavigationHelpers; -} - -export const ProfileLoadPage: React.FC = ({navigation}) => { +export const ProfileLoadPage: React.FC = () => { const { loadingDb, database } = useContext(AppContext) const { publicKey, relayPool, lastEventId, loadingRelayPool } = useContext(RelayPoolContext) const { t } = useTranslation('common') @@ -28,6 +24,10 @@ export const ProfileLoadPage: React.FC = ({navigation}) => kinds: [EventKind.petNames, EventKind.meta], authors: [publicKey], }, + { + kinds: [EventKind.petNames], + '#p': [publicKey], + }, ]) } }, [loadingRelayPool, publicKey, loadingDb]) @@ -74,15 +74,14 @@ export const ProfileLoadPage: React.FC = ({navigation}) => {t('profileLoadPage.foundContacts', { contactsCount })} - - {}} - action={{label: t('profileLoadPage.relays') ?? '', onPress: () => navigation.navigate('Relays')}} + action={{label: t('profileLoadPage.relays') ?? '', onPress: () => navigate('Relays')}} > Conéctate a otros relays si tienes problemas encontrando tus datos. diff --git a/frontend/Pages/RelaysPage/index.tsx b/frontend/Pages/RelaysPage/index.tsx index d860102..0cf1119 100644 --- a/frontend/Pages/RelaysPage/index.tsx +++ b/frontend/Pages/RelaysPage/index.tsx @@ -1,9 +1,8 @@ -import React, { useContext, useEffect, useState } from 'react' +import React, { useContext, useState } from 'react' import { Clipboard, FlatList, ListRenderItem, StyleSheet, View } from 'react-native' -import { AppContext } from '../../Contexts/AppContext' import { useTranslation } from 'react-i18next' import { RelayPoolContext } from '../../Contexts/RelayPoolContext' -import { getRelays, Relay } from '../../Functions/DatabaseFunctions/Relays' +import { Relay } from '../../Functions/DatabaseFunctions/Relays' import { defaultRelays, REGEX_SOCKET_LINK } from '../../Constants/Relay' import { Snackbar, @@ -21,55 +20,35 @@ import RBSheet from 'react-native-raw-bottom-sheet' export const RelaysPage: React.FC = () => { const defaultRelayInput = React.useMemo(() => 'wss://', []) - const { database } = useContext(AppContext) - const { relayPool, publicKey } = useContext(RelayPoolContext) + const { addRelayItem, removeRelayItem, relays } = useContext(RelayPoolContext) const { t } = useTranslation('common') const theme = useTheme() const bottomSheetAddRef = React.useRef(null) const bottomSheetEditRef = React.useRef(null) - const [relays, setRelays] = useState([]) const [selectedRelay, setSelectedRelay] = useState() const [addRelayInput, setAddRelayInput] = useState(defaultRelayInput) const [showNotification, setShowNotification] = useState<'remove' | 'add' | 'badFormat'>() - const loadRelays: () => void = () => { - if (database) { - getRelays(database).then((results) => { - if (results) { - setRelays(results) - } - }) - } + const addRelay: (url: string) => void = (url) => { + addRelayItem({ + url, + }).then(() => { + setShowNotification('add') + }) } - useEffect(loadRelays, []) - - const addRelayItem: (relay: Relay) => void = async (relay) => { - if (relayPool && database && publicKey) { - setRelays((prev) => [...prev, relay]) - relayPool.add(relay.url, () => { - setShowNotification('add') - loadRelays() - }) - } - } - - const removeRelayItem: (relay: Relay) => void = async (relay) => { - if (relayPool && database && publicKey) { - setRelays((prev) => prev.filter((item) => item.url !== relay.url)) - relayPool.remove(relay.url, () => { - setShowNotification('remove') - loadRelays() - }) - } + const removeRelay: (url: string) => void = (url) => { + removeRelayItem({ + url, + }).then(() => { + setShowNotification('remove') + }) } const onPressAddRelay: () => void = () => { if (REGEX_SOCKET_LINK.test(addRelayInput)) { bottomSheetAddRef.current?.close() - addRelayItem({ - url: addRelayInput, - }) + setAddRelayInput(defaultRelayInput) } else { bottomSheetAddRef.current?.close() @@ -91,7 +70,7 @@ export const RelaysPage: React.FC = () => { const active = relays?.some((item) => item.url === relay.url) const onValueChange: () => void = () => { - active ? removeRelayItem(relay) : addRelayItem(relay) + active ? removeRelay(relay.url) : addRelay(relay.url) } return @@ -183,7 +162,7 @@ export const RelaysPage: React.FC = () => { icon='trash-can-outline' size={28} onPress={() => { - if (selectedRelay) removeRelayItem(selectedRelay) + if (selectedRelay) removeRelay(selectedRelay.url) bottomSheetEditRef.current?.close() }} /> diff --git a/frontend/index.tsx b/frontend/index.tsx index 533ae2f..07b3160 100644 --- a/frontend/index.tsx +++ b/frontend/index.tsx @@ -13,6 +13,7 @@ import { adaptNavigationTheme, Provider as PaperProvider } from 'react-native-pa import { SafeAreaProvider, SafeAreaInsetsContext } from 'react-native-safe-area-context' import i18n from './i18n.config' import nostrosDarkTheme from './Constants/Theme/theme-dark.json' +import { navigationRef } from './lib/Navigation' import HomeNavigator from './Pages/HomeNavigator' import MenuItems from './Components/MenuItems' import FeedNavigator from './Pages/FeedNavigator' @@ -20,8 +21,6 @@ import FeedNavigator from './Pages/FeedNavigator' const DrawerNavigator = createDrawerNavigator() export const Frontend: React.FC = () => { - const [initialState] = React.useState() - const { DarkTheme } = adaptNavigationTheme({ reactNavigationLight: NavigationDefaultTheme, reactNavigationDark: NavigationDarkTheme, @@ -41,18 +40,19 @@ export const Frontend: React.FC = () => { - - - - + + + + {() => { return ( } + drawerContent={({ navigation }) => } screenOptions={{ drawerStyle: { - borderRadius: 28 + borderRadius: 28, + width: 296 }, }} > @@ -70,10 +70,10 @@ export const Frontend: React.FC = () => { ) }} - - - - + + + + diff --git a/frontend/lib/Navigation/index.ts b/frontend/lib/Navigation/index.ts new file mode 100644 index 0000000..636ed4f --- /dev/null +++ b/frontend/lib/Navigation/index.ts @@ -0,0 +1,9 @@ +import { createNavigationContainerRef } from '@react-navigation/native'; + +export const navigationRef = createNavigationContainerRef() + +export const navigate: (name: string, params?: any) => void = (name, params ={}) => { + if (navigationRef.isReady()) { + navigationRef.navigate(name as never, params as never); + } +} \ No newline at end of file From 1a97246dd3952fe5a219357d88cbe8dff8a326f1 Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Sat, 14 Jan 2023 21:35:35 +0100 Subject: [PATCH 2/2] New UI Profile --- frontend/Components/Avatar/index.tsx | 6 +- frontend/Components/LnPayment/index.tsx | 266 ++++---- frontend/Components/MenuItems/index.tsx | 89 +-- frontend/Components/NavigationBar/index.tsx | 3 +- frontend/Components/NoteCard/index.tsx | 3 +- frontend/Contexts/AppContext.tsx | 2 - frontend/Contexts/RelayPoolContext.tsx | 44 +- frontend/Contexts/UserContext.tsx | 143 +++++ frontend/Pages/ContactsPage/index.tsx | 6 +- frontend/Pages/FeedNavigator/index.tsx | 4 +- frontend/Pages/ProfileConfigPage/index.tsx | 667 ++++++++++++++------ frontend/Pages/ProfileConnectPage/index.tsx | 9 +- frontend/Pages/ProfileCreatePage/index.tsx | 4 +- frontend/Pages/ProfileLoadPage/index.tsx | 39 +- frontend/Pages/ProfilePage/index.tsx | 394 +----------- frontend/Pages/ProfilePageOld/index.tsx | 342 ++++++++++ frontend/Pages/RelaysPage/index.tsx | 41 +- frontend/index.tsx | 64 +- frontend/lib/Navigation/index.ts | 6 + package.json | 3 +- yarn.lock | 41 +- 21 files changed, 1305 insertions(+), 871 deletions(-) create mode 100644 frontend/Contexts/UserContext.tsx create mode 100644 frontend/Pages/ProfilePageOld/index.tsx diff --git a/frontend/Components/Avatar/index.tsx b/frontend/Components/Avatar/index.tsx index 6056740..33e7611 100644 --- a/frontend/Components/Avatar/index.tsx +++ b/frontend/Components/Avatar/index.tsx @@ -14,6 +14,7 @@ export const NostrosAvatar: React.FC = ({ src, name, pubKey, size = const theme = useTheme() const displayName = name && name !== '' ? name : pubKey const hasLud06 = lud06 && lud06 !== '' + const lud06IconSize = size / 2.85 const validImage: () => boolean = () => { if (src) { const regexp = /^(https?:\/\/.*\.(?:png|jpg|jpeg))$/ @@ -32,11 +33,11 @@ export const NostrosAvatar: React.FC = ({ src, name, pubKey, size = )} {hasLud06 && ( @@ -47,7 +48,6 @@ export const NostrosAvatar: React.FC = ({ src, name, pubKey, size = const styles = StyleSheet.create({ iconLightning: { - top: -14, }, }) diff --git a/frontend/Components/LnPayment/index.tsx b/frontend/Components/LnPayment/index.tsx index bfb40f2..3a42bbf 100644 --- a/frontend/Components/LnPayment/index.tsx +++ b/frontend/Components/LnPayment/index.tsx @@ -1,11 +1,12 @@ import React, { useEffect, useState } from 'react' import { requestInvoice } from 'lnurl-pay' -import { Button, Card, Input, Layout, Modal, Text } from '@ui-kitten/components' +import QRCode from 'react-native-qrcode-svg' import { Event } from '../../lib/nostr/Events' import { User } from '../../Functions/DatabaseFunctions/Users' -import { Clipboard, Linking, StyleSheet } from 'react-native' +import { Clipboard, Linking, StyleSheet, View } from 'react-native' import { useTranslation } from 'react-i18next' -import { showMessage } from 'react-native-flash-message' +import RBSheet from 'react-native-raw-bottom-sheet' +import { Button, Card, IconButton, Text, TextInput, useTheme } from 'react-native-paper' interface TextContentProps { open: boolean @@ -15,73 +16,41 @@ interface TextContentProps { } export const LnPayment: React.FC = ({ open, setOpen, event, user }) => { + const theme = useTheme() const { t } = useTranslation('common') + const bottomSheetLnPaymentRef = React.useRef(null) + const bottomSheetInvoiceRef = React.useRef(null) const [monto, setMonto] = useState('') const defaultComment = event?.id ? `Tip for Nostr event ${event?.id}` : '' const [comment, setComment] = useState(defaultComment) + const [invoice, setInvoice] = useState() const [loading, setLoading] = useState(false) useEffect(() => { setMonto('') + setInvoice(undefined) + if (open) { + bottomSheetLnPaymentRef.current?.open() + } else { + bottomSheetLnPaymentRef.current?.close() + bottomSheetInvoiceRef.current?.close() + } }, [open]) useEffect(() => { setComment(defaultComment) }, [event, open]) - const styles = StyleSheet.create({ - modal: { - paddingLeft: 32, - paddingRight: 32, - width: '100%', - }, - backdrop: { - backgroundColor: 'rgba(0, 0, 0, 0.5)', - }, - input: { - marginTop: 31, - }, - modalContainer: { - marginBottom: 15, - }, - buttonsContainer: { - flexDirection: 'row', - marginTop: 31, - }, - buttonLeft: { - flex: 3, - paddingRight: 16, - }, - buttonRight: { - flex: 3, - paddingLeft: 16, - }, - buttonMonto: { - flex: 2, - }, - buttonMontoMiddle: { - flex: 2, - marginLeft: 10, - marginRight: 10, - }, - satoshi: { - fontFamily: 'Satoshi-Symbol', - }, - }) - - const copyInvoice: (invoice: string) => void = (invoice) => { - Clipboard.setString(invoice) - showMessage({ - message: t('alerts.invoiceCopied'), - type: 'success', - }) + const copyInvoice: () => void = () => { + console.log(invoice) + Clipboard.setString(invoice ?? '') } - const openApp: (invoice: string) => void = (invoice) => { + const openApp: () => void = () => { Linking.openURL(`lightning:${invoice}`) } - const generateInvoice: (copy: boolean) => void = async (copy) => { + const generateInvoice: () => void = async () => { if (user?.lnurl && monto !== '') { setLoading(true) requestInvoice({ @@ -91,98 +60,161 @@ export const LnPayment: React.FC = ({ open, setOpen, event, us }) .then((action) => { if (action.hasValidAmount && action.invoice) { - copy ? copyInvoice(action.invoice) : openApp(action.invoice) - } else { - showMessage({ - message: t('alerts.invoiceError'), - type: 'danger', - }) + setInvoice(action.invoice) + bottomSheetInvoiceRef.current?.open() } setLoading(false) - setOpen(false) - setMonto('') - setComment('') }) .catch(() => setLoading(false)) } } + const rbSheetCustomStyles = React.useMemo(() => { + return { + container: { + ...styles.rbsheetContainer, + backgroundColor: theme.colors.background, + }, + draggableIcon: styles.rbsheetDraggableIcon, + } + }, []) + return user?.lnurl ? ( - setOpen(false)} - > - - - - - - - - - { - if (/^\d+$/.test(text)) { - setMonto(text) - } - }} - size='large' - placeholder={t('lnPayment.monto')} - accessoryLeft={() => s} - /> - - - - - - - - - - - - - - - + + + + + + + + setOpen(false)} + > + + + + + + + + s + + {monto} + + {comment && ( + + {comment} + + )} + + + + + + {t('profileConfigPage.copyNPub')} + + + + {t('profileConfigPage.invoice')} + + + + + + ) : ( <> ) } +const styles = StyleSheet.create({ + qrContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + qrText: { + marginTop: 20, + flexDirection: 'row', + justifyContent: 'center', + }, + rbsheetDraggableIcon: { + backgroundColor: '#000', + }, + rbsheetContainer: { + padding: 16, + borderTopRightRadius: 28, + borderTopLeftRadius: 28, + }, + satoshi: { + fontFamily: 'Satoshi-Symbol', + fontSize: 20, + }, + montoSelection: { + flexDirection: 'row', + }, + montoButton: { + flex: 2, + }, + actionButton: { + justifyContent: 'center', + alignItems: 'center', + width: 80, + }, + cardActions: { + flexDirection: 'row', + justifyContent: 'space-around', + }, +}) + export default LnPayment diff --git a/frontend/Components/MenuItems/index.tsx b/frontend/Components/MenuItems/index.tsx index 163f4db..c92f1e9 100644 --- a/frontend/Components/MenuItems/index.tsx +++ b/frontend/Components/MenuItems/index.tsx @@ -1,22 +1,23 @@ import * as React from 'react' import { StyleSheet, View } from 'react-native' import { DrawerContentScrollView } from '@react-navigation/drawer' -import { Button, Card, Chip, Drawer, IconButton, Text, useTheme } from 'react-native-paper' +import { + Button, + Card, + Chip, + Drawer, + IconButton, + Text, + TouchableRipple, + useTheme, +} from 'react-native-paper' import Logo from '../Logo' import { useTranslation } from 'react-i18next' -import SInfo from 'react-native-sensitive-info' import { RelayPoolContext } from '../../Contexts/RelayPoolContext' -import { dropTables } from '../../Functions/DatabaseFunctions' -import { AppContext } from '../../Contexts/AppContext' +import { UserContext } from '../../Contexts/UserContext' import { DrawerNavigationHelpers } from '@react-navigation/drawer/lib/typescript/src/types' import { navigate } from '../../lib/Navigation' import NostrosAvatar from '../Avatar' -import { - getContactsCount, - getFollowersCount, - getUser, - User, -} from '../../Functions/DatabaseFunctions/Users' import { formatPubKey } from '../../Functions/RelayFunctions/Users' interface MenuItemsProps { @@ -25,38 +26,14 @@ interface MenuItemsProps { export const MenuItems: React.FC = ({ navigation }) => { const [drawerItemIndex, setDrawerItemIndex] = React.useState(-1) - const { database, init } = React.useContext(AppContext) - const { setPrivateKey, setPublicKey, relayPool, publicKey, relays } = React.useContext(RelayPoolContext) - const [user, setUser] = React.useState() - const [contactsCount, setContantsCount] = React.useState() - const [followersCount, setFollowersCount] = React.useState() + const { relays } = React.useContext(RelayPoolContext) + const { nPub, publicKey, user, contactsCount, followersCount, logout } = + React.useContext(UserContext) const { t } = useTranslation('common') const theme = useTheme() - React.useEffect(() => { - if (database && publicKey) { - getUser(publicKey, database).then((result) => { - if (result) setUser(result) - }) - getContactsCount(database).then(setContantsCount) - getFollowersCount(database).then(setFollowersCount) - } - }, [publicKey, database]) - const onPressLogout: () => void = () => { - if (database) { - relayPool?.unsubscribeAll() - setPrivateKey(undefined) - setPublicKey(undefined) - dropTables(database).then(() => { - SInfo.deleteItem('privateKey', {}).then(() => { - SInfo.deleteItem('publicKey', {}).then(() => { - init() - navigate('Home', { screen: 'ProfileConnect' }) - }) - }) - }) - } + logout() } const onPressItem: (key: string, index: number) => void = (key, index) => { @@ -85,23 +62,27 @@ export const MenuItems: React.FC = ({ navigation }) => { - {user && ( + {nPub && ( - - - - - {user.name} - {formatPubKey(user.id)} - + navigate('Profile')}> + + + + + + {user?.name} + {formatPubKey(nPub)} + + + - console.log('Pressed')} /> + navigate('ProfileConfig')} /> @@ -141,14 +122,14 @@ export const MenuItems: React.FC = ({ navigation }) => { ) } /> - onPressItem('config', 1)} onTouchEnd={() => setDrawerItemIndex(-1)} - /> + /> */} )} diff --git a/frontend/Components/NavigationBar/index.tsx b/frontend/Components/NavigationBar/index.tsx index e17261a..ba5bb34 100644 --- a/frontend/Components/NavigationBar/index.tsx +++ b/frontend/Components/NavigationBar/index.tsx @@ -3,10 +3,11 @@ import { BottomNavigation, BottomNavigationTab, useTheme } from '@ui-kitten/comp import { AppContext } from '../../Contexts/AppContext' import Icon from 'react-native-vector-icons/FontAwesome5' import { RelayPoolContext } from '../../Contexts/RelayPoolContext' +import { UserContext } from '../../Contexts/UserContext' export const NavigationBar: React.FC = () => { const { goToPage, getActualPage, page } = useContext(AppContext) - const { publicKey, privateKey } = useContext(RelayPoolContext) + const { publicKey, privateKey } = React.useContext(UserContext) const theme = useTheme() const profilePage = `profile#${publicKey ?? ''}` diff --git a/frontend/Components/NoteCard/index.tsx b/frontend/Components/NoteCard/index.tsx index 3a99ce0..b053885 100644 --- a/frontend/Components/NoteCard/index.tsx +++ b/frontend/Components/NoteCard/index.tsx @@ -35,7 +35,8 @@ export const NoteCard: React.FC = ({ onlyContactsReplies = false, }) => { const theme = useTheme() - const { relayPool, publicKey, privateKey, lastEventId } = useContext(RelayPoolContext) + const { publicKey, privateKey } = React.useContext(UserContext) + const { relayPool, lastEventId } = useContext(RelayPoolContext) const { database, goToPage } = useContext(AppContext) const [relayAdded, setRelayAdded] = useState(false) const [replies, setReplies] = useState([]) diff --git a/frontend/Contexts/AppContext.tsx b/frontend/Contexts/AppContext.tsx index 784f717..210de17 100644 --- a/frontend/Contexts/AppContext.tsx +++ b/frontend/Contexts/AppContext.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useState } from 'react' import { QuickSQLiteConnection } from 'react-native-quick-sqlite' import { initDatabase } from '../Functions/DatabaseFunctions' import SInfo from 'react-native-sensitive-info' -import { navigate } from '../lib/Navigation' export interface AppContextProps { init: () => void @@ -29,7 +28,6 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E setDatabase(db) SInfo.getItem('publicKey', {}).then((value) => { setLoadingDb(false) - if (value) navigate('Feed') }) } diff --git a/frontend/Contexts/RelayPoolContext.tsx b/frontend/Contexts/RelayPoolContext.tsx index 7a3307b..9dc1a39 100644 --- a/frontend/Contexts/RelayPoolContext.tsx +++ b/frontend/Contexts/RelayPoolContext.tsx @@ -1,20 +1,15 @@ import React, { useContext, useEffect, useMemo, useState } from 'react' import RelayPool from '../lib/nostr/RelayPool/intex' import { AppContext } from './AppContext' -import SInfo from 'react-native-sensitive-info' -import { getPublickey } from '../lib/nostr/Bip' import { DeviceEventEmitter } from 'react-native' import debounce from 'lodash.debounce' import { getRelays, Relay } from '../Functions/DatabaseFunctions/Relays' +import { UserContext } from './UserContext' export interface RelayPoolContextProps { loadingRelayPool: boolean relayPool?: RelayPool setRelayPool: (relayPool: RelayPool) => void - publicKey?: string - setPublicKey: (privateKey: string | undefined) => void - privateKey?: string - setPrivateKey: (privateKey: string | undefined) => void lastEventId?: string lastConfirmationtId?: string relays: Relay[] @@ -33,8 +28,6 @@ export interface RelayPoolContextProviderProps { export const initialRelayPoolContext: RelayPoolContextProps = { loadingRelayPool: true, - setPublicKey: () => {}, - setPrivateKey: () => {}, setRelayPool: () => {}, addRelayItem: async () => await new Promise(() => {}), removeRelayItem: async () => await new Promise(() => {}), @@ -45,10 +38,9 @@ export const RelayPoolContextProvider = ({ children, images, }: RelayPoolContextProviderProps): JSX.Element => { - const { database, loadingDb } = useContext(AppContext) + const { database } = useContext(AppContext) + const { publicKey, privateKey } = React.useContext(UserContext) - const [publicKey, setPublicKey] = useState() - const [privateKey, setPrivateKey] = useState() const [relayPool, setRelayPool] = useState() const [loadingRelayPool, setLoadingRelayPool] = useState( initialRelayPoolContext.loadingRelayPool, @@ -116,46 +108,16 @@ export const RelayPoolContextProvider = ({ useEffect(() => { if (publicKey && publicKey !== '') { - SInfo.setItem('publicKey', publicKey, {}) loadRelayPool() } }, [publicKey]) - useEffect(() => { - if (privateKey && privateKey !== '') { - SInfo.setItem('privateKey', privateKey, {}) - const publicKey: string = getPublickey(privateKey) - setPublicKey(publicKey) - } - }, [privateKey]) - - useEffect(() => { - if (!loadingDb) { - SInfo.getItem('privateKey', {}).then((privateResult) => { - if (privateResult && privateResult !== '') { - setPrivateKey(privateResult) - setPublicKey(getPublickey(privateResult)) - } else { - SInfo.getItem('publicKey', {}).then((publicResult) => { - if (publicResult && publicResult !== '') { - setPublicKey(publicResult) - } - }) - } - }) - } - }, [loadingDb]) - return ( void + privateKey?: string + setPrivateKey: (privateKey: string | undefined) => void + setUser: (user: User) => void + user?: User, + contactsCount: number, + followersCount: number, + reloadUser: () => void + logout: () => void +} + +export interface UserContextProviderProps { + children: React.ReactNode +} + +export const initialUserContext: UserContextProps = { + setPublicKey: () => {}, + setPrivateKey: () => {}, + setUser: () => {}, + reloadUser: () => {}, + logout: () => {}, + contactsCount: 0, + followersCount: 0 +} + +export const UserContextProvider = ({ children }: UserContextProviderProps): JSX.Element => { + const { database, loadingDb, init } = useContext(AppContext) + const { relayPool } = React.useContext(RelayPoolContext) + const [publicKey, setPublicKey] = useState() + const [nPub, setNpub] = useState() + const [nSec, setNsec] = useState() + const [privateKey, setPrivateKey] = useState() + const [user, setUser] = React.useState() + const [contactsCount, setContantsCount] = React.useState(0) + const [followersCount, setFollowersCount] = React.useState(0) + + const reloadUser: () => void = () => { + if (database && publicKey) { + getUser(publicKey, database).then((result) => { + if (result) setUser(result) + }) + getContactsCount(database).then(setContantsCount) + getFollowersCount(database).then(setFollowersCount) + } + } + + const logout: () => void = () => { + if (database) { + relayPool?.unsubscribeAll() + setPrivateKey(undefined) + setPublicKey(undefined) + setNpub(undefined) + setNsec(undefined) + setUser(undefined) + dropTables(database).then(() => { + SInfo.deleteItem('privateKey', {}).then(() => { + SInfo.deleteItem('publicKey', {}).then(() => { + init() + navigate('Home', { screen: 'ProfileConnect' }) + }) + }) + }) + } + } + + useEffect(() => { + if (privateKey && privateKey !== '') { + SInfo.setItem('privateKey', privateKey, {}) + setNsec(nsecEncode(privateKey)) + const publicKey: string = getPublickey(privateKey) + setPublicKey(publicKey) + } + }, [privateKey]) + + useEffect(() => { + if (publicKey && publicKey !== '') { + SInfo.setItem('publicKey', publicKey, {}) + setNpub(npubEncode(publicKey)) + reloadUser() + } + }, [publicKey]) + + useEffect(() => { + if (user) { + navigate('Feed') + } + }, [user]) + + useEffect(() => { + if (!loadingDb ) { + SInfo.getItem('privateKey', {}).then((privateResult) => { + if (privateResult && privateResult !== '') { + setPrivateKey(privateResult) + setPublicKey(getPublickey(privateResult)) + } else { + SInfo.getItem('publicKey', {}).then((publicResult) => { + if (publicResult && publicResult !== '') { + setPublicKey(publicResult) + jumpTo('Feed') + } + }) + } + }) + } + }, [loadingDb]) + + return ( + + {children} + + ) +} + +export const UserContext = React.createContext(initialUserContext) diff --git a/frontend/Pages/ContactsPage/index.tsx b/frontend/Pages/ContactsPage/index.tsx index 9d60a6d..24b1cad 100644 --- a/frontend/Pages/ContactsPage/index.tsx +++ b/frontend/Pages/ContactsPage/index.tsx @@ -20,10 +20,12 @@ import { Button, UserCard } from '../../Components' import { RelayPoolContext } from '../../Contexts/RelayPoolContext' import { populatePets } from '../../Functions/RelayFunctions/Users' import { getNip19Key } from '../../lib/nostr/Nip19' +import { UserContext } from '../../Contexts/UserContext' export const ContactsPage: React.FC = () => { - const { database, goBack } = useContext(AppContext) - const { relayPool, publicKey, privateKey, lastEventId } = useContext(RelayPoolContext) + const { database } = useContext(AppContext) + const { publicKey, privateKey } = React.useContext(UserContext) + const { relayPool, lastEventId } = useContext(RelayPoolContext) const theme = useTheme() // State const [users, setUsers] = useState() diff --git a/frontend/Pages/FeedNavigator/index.tsx b/frontend/Pages/FeedNavigator/index.tsx index eddb41a..c5e47b6 100644 --- a/frontend/Pages/FeedNavigator/index.tsx +++ b/frontend/Pages/FeedNavigator/index.tsx @@ -9,7 +9,7 @@ import HomePage from '../HomePage' import RelaysPage from '../RelaysPage' import AboutPage from '../AboutPage' import ProfileConfigPage from '../ProfileConfigPage' -import { navigate } from '../../lib/Navigation' +import ProfilePage from '../ProfilePage' export const HomeNavigator: React.FC = () => { const theme = useTheme() @@ -44,7 +44,6 @@ export const HomeNavigator: React.FC = () => { /> ) : null} - navigate('Config')} /> ) }, @@ -58,6 +57,7 @@ export const HomeNavigator: React.FC = () => { + { const theme = useTheme() - const { goToPage, goBack, database, init } = useContext(AppContext) - const { setPrivateKey, setPublicKey, relayPool, publicKey, privateKey } = - useContext(RelayPoolContext) + const bottomSheetPictureRef = React.useRef(null) + const bottomSheetDirectoryRef = React.useRef(null) + const bottomSheetNip05Ref = React.useRef(null) + const bottomSheetLud06Ref = React.useRef(null) + const { database } = useContext(AppContext) + const { relayPool } = useContext(RelayPoolContext) + const { user, publicKey, nPub, nSec, contactsCount, followersCount, setUser } = + useContext(UserContext) // State const [name, setName] = useState() const [picture, setPicture] = useState() @@ -23,202 +37,501 @@ export const ProfileConfigPage: React.FC = () => { const [lnurl, setLnurl] = useState() const [isPublishingProfile, setIsPublishingProfile] = useState(false) const [nip05, setNip05] = useState() + const [showNotification, setShowNotification] = useState< + | 'npubCopied' + | 'picturePublished' + | 'connectionError' + | 'nsecCopied' + | 'profilePublished' + | 'nip05Published' + | 'lud06Published' + >() const { t } = useTranslation('common') useEffect(() => { relayPool?.unsubscribeAll() if (database && publicKey) { + if (user) { + setName(user.name) + setPicture(user.picture) + setAbout(user.about) + setLnurl(user.lnurl) + setNip05(user.nip05) + } + } + }, [user]) + + const onPressSavePicture: () => void = () => { + if (publicKey && database) { getUser(publicKey, database).then((user) => { if (user) { - setName(user.name) - setPicture(user.picture) - setAbout(user.about) - setLnurl(user.lnurl) - setNip05(user.nip05) + relayPool + ?.sendEvent({ + content: JSON.stringify({ + name: user.name, + about: user.about, + picture, + lud06: user.lnurl, + nip05: user.nip05, + }), + created_at: moment().unix(), + kind: EventKind.meta, + pubkey: publicKey, + tags: [], + }) + .then(() => { + setIsPublishingProfile(false) // restore sending status + setShowNotification('picturePublished') + setUser({ + ...user, + picture, + }) + bottomSheetPictureRef.current?.close() + }) + .catch(() => { + setIsPublishingProfile(false) // restore sending status + setShowNotification('connectionError') + }) } }) } - }, []) - - const onPressBack: () => void = () => { - relayPool?.unsubscribeAll() - goBack() } - const onPressLogout: () => void = () => { - if (database) { - relayPool?.unsubscribeAll() - setPrivateKey(undefined) - setPublicKey(undefined) - dropTables(database).then(() => { - SInfo.deleteItem('privateKey', {}).then(() => { - SInfo.deleteItem('publicKey', {}).then(() => { - init() - goToPage('landing', true) - }) - }) + const onPressSaveNip05: () => void = () => { + if (publicKey && database) { + getUser(publicKey, database).then((user) => { + if (user) { + relayPool + ?.sendEvent({ + content: JSON.stringify({ + name: user.name, + about: user.about, + picture: user.picture, + lud06: user.lnurl, + nip05, + }), + created_at: moment().unix(), + kind: EventKind.meta, + pubkey: publicKey, + tags: [], + }) + .then(() => { + setIsPublishingProfile(false) // restore sending status + setShowNotification('nip05Published') + setUser({ + ...user, + nip05, + }) + bottomSheetNip05Ref.current?.close() + }) + .catch(() => { + setIsPublishingProfile(false) // restore sending status + setShowNotification('connectionError') + }) + } }) } } - const onPushPublishProfile: () => void = () => { - if (publicKey) { - setIsPublishingProfile(true) - relayPool - ?.sendEvent({ - content: JSON.stringify({ - name, - about, - picture, - lud06: lnurl, - nip05, - }), - created_at: moment().unix(), - kind: EventKind.meta, - pubkey: publicKey, - tags: [], - }) - .then(() => { - setIsPublishingProfile(false) // restore sending status - }) - .catch((err) => { - setIsPublishingProfile(false) // restore sending status - }) + const onPressSaveLnurl: () => void = () => { + if (publicKey && database) { + getUser(publicKey, database).then((user) => { + if (user) { + relayPool + ?.sendEvent({ + content: JSON.stringify({ + name: user.name, + about: user.about, + picture: user.picture, + lnurl, + nip05: user.nip05, + }), + created_at: moment().unix(), + kind: EventKind.meta, + pubkey: publicKey, + tags: [], + }) + .then(() => { + setIsPublishingProfile(false) // restore sending status + setShowNotification('lud06Published') + setUser({ + ...user, + lnurl, + }) + bottomSheetLud06Ref.current?.close() + }) + .catch(() => { + setIsPublishingProfile(false) // restore sending status + setShowNotification('connectionError') + }) + } + }) } } - const copyToClipboard: (value: string) => JSX.Element = (value) => { - const copy: () => void = () => Clipboard.setString(value) - - return + const onPressSaveProfile: () => void = () => { + if (publicKey && database) { + getUser(publicKey, database).then((user) => { + if (user) { + relayPool + ?.sendEvent({ + content: JSON.stringify({ + name, + about, + picture: user.picture, + lud06: lnurl, + nip05: user.nip05, + }), + created_at: moment().unix(), + kind: EventKind.meta, + pubkey: publicKey, + tags: [], + }) + .then(() => { + setIsPublishingProfile(false) // restore sending status + setShowNotification('profilePublished') + bottomSheetPictureRef.current?.close() + }) + .catch(() => { + setIsPublishingProfile(false) // restore sending status + setShowNotification('connectionError') + }) + setUser({ + ...user, + name, + about, + picture, + lnurl, + nip05, + }) + } + }) + } } - const styles = StyleSheet.create({ - container: { - flex: 1, - }, - actionContainer: { - marginTop: 30, - paddingLeft: 32, - paddingRight: 32, - paddingBottom: 32, - }, - action: { - backgroundColor: 'transparent', - marginTop: 30, - }, - }) + const rbSheetCustomStyles = React.useMemo(() => { + return { + container: { + ...styles.rbsheetContainer, + backgroundColor: theme.colors.background, + }, + draggableIcon: styles.rbsheetDraggableIcon, + } + }, []) + + const pastePicture: () => void = () => { + Clipboard.getString().then((value) => { + setPicture(value ?? '') + }) + } + + const pasteNip05: () => void = () => { + Clipboard.getString().then((value) => { + setNip05(value ?? '') + }) + } + + const pasteLud06: () => void = () => { + Clipboard.getString().then((value) => { + setLnurl(value ?? '') + }) + } return ( - <> - {/* - + + + + + bottomSheetPictureRef.current?.open()}> + {user?.picture ? ( + + ) : ( + + )} + + + + + + + + + { + setShowNotification('picturePublished') + Clipboard.setString(nPub ?? '') + }} + /> + {t('profileConfigPage.copyNPub')} + + + bottomSheetDirectoryRef.current?.open()} + /> + {t('profileConfigPage.directory')} + + + bottomSheetLud06Ref.current?.open()} + /> + {t('profileConfigPage.invoice')} + + + + + bottomSheetNip05Ref.current?.open()} + /> + {t('profileConfigPage.nip05')} + + + + + + + - - - - - - - - - - + + { + setShowNotification('npubCopied') + Clipboard.setString(nPub ?? '') + }} + forceTextInputFocus={false} + /> + } + /> + { + setShowNotification('nsecCopied') + Clipboard.setString(nSec ?? '') + }} + forceTextInputFocus={false} + /> + } + /> + + + + + {t('profileConfigPage.pictureTitle')} + {t('profileConfigPage.pictureDescription')} + - - - + + + + + + {t('profileConfigPage.directoryTitle')} + {t('profileConfigPage.directoryDescription')} + + + + + + + {t('profileConfigPage.pictureTitle')} + {t('profileConfigPage.pictureDescription')} + - - - + + + + + + {t('profileConfigPage.lud06Title')} + {t('profileConfigPage.lud06Description')} + - - - - - - - - - - - - - - - copyToClipboard(publicKey ?? '')} - value={publicKey} - label={t('configPage.publicKey')} - /> - - - copyToClipboard(privateKey ?? '')} - value={privateKey} - secureTextEntry={true} - label={t('configPage.privateKey')} - /> - - - - - - - */} - + } + /> + + + + setShowNotification(undefined)} + onDismiss={() => setShowNotification(undefined)} + > + {t(`profileConfigPage.${showNotification}`)} + + ) } +const styles = StyleSheet.create({ + container: { + padding: 16, + }, + cardContainer: { + width: '100%', + justifyContent: 'center', + alignContent: 'center', + }, + cardActions: { + flexDirection: 'row', + justifyContent: 'space-around', + }, + cardPicture: { + flexDirection: 'row', + justifyContent: 'center', + alignContent: 'center', + marginBottom: 32, + }, + actionButton: { + marginTop: 32, + justifyContent: 'center', + alignItems: 'center', + width: 80, + }, + rbsheetDraggableIcon: { + backgroundColor: '#000', + }, + rbsheetContainer: { + padding: 16, + borderTopRightRadius: 28, + borderTopLeftRadius: 28, + }, + snackbar: { + margin: 16, + bottom: 70, + }, +}) + export default ProfileConfigPage diff --git a/frontend/Pages/ProfileConnectPage/index.tsx b/frontend/Pages/ProfileConnectPage/index.tsx index ac06cdb..6c81cc9 100644 --- a/frontend/Pages/ProfileConnectPage/index.tsx +++ b/frontend/Pages/ProfileConnectPage/index.tsx @@ -1,6 +1,6 @@ import React, { useContext, useEffect, useState } from 'react' import { Clipboard, StyleSheet, View } from 'react-native' -import { RelayPoolContext } from '../../Contexts/RelayPoolContext' +import { UserContext } from '../../Contexts/UserContext' import { useTranslation } from 'react-i18next' import { getNip19Key, isPrivateKey, isPublicKey } from '../../lib/nostr/Nip19' import { Button, Switch, Text, TextInput } from 'react-native-paper' @@ -8,17 +8,13 @@ import Logo from '../../Components/Logo' import { navigate } from '../../lib/Navigation' export const ProfileConnectPage: React.FC = () => { - const { setPrivateKey, setPublicKey } = useContext(RelayPoolContext) + const { setPrivateKey, setPublicKey } = useContext(UserContext) const { t } = useTranslation('common') const [isNip19, setIsNip19] = useState(false) const [isPublic, setIsPublic] = useState(false) const [inputValue, setInputValue] = useState('') useEffect(() => checkKey(), [inputValue]) - useEffect(() => { - setPrivateKey(undefined) - setPublicKey(undefined) - }, []) const checkKey: () => void = () => { if (inputValue && inputValue !== '') { @@ -48,7 +44,6 @@ export const ProfileConnectPage: React.FC = () => { Clipboard.getString().then((value) => { setInputValue(value ?? '') }) - } const label: string = React.useMemo(() => isPublic ? t('loggerPage.publicKey') : t('loggerPage.privateKey'), [isPublic]) diff --git a/frontend/Pages/ProfileCreatePage/index.tsx b/frontend/Pages/ProfileCreatePage/index.tsx index c38af8c..77c5e2b 100644 --- a/frontend/Pages/ProfileCreatePage/index.tsx +++ b/frontend/Pages/ProfileCreatePage/index.tsx @@ -4,8 +4,8 @@ import { Clipboard, StyleSheet, View } from 'react-native' import { Button, Snackbar, TextInput } from 'react-native-paper' import { useTranslation } from 'react-i18next' import { nsecEncode } from 'nostr-tools/nip19' -import { RelayPoolContext } from '../../Contexts/RelayPoolContext' import { DrawerNavigationHelpers } from '@react-navigation/drawer/lib/typescript/src/types' +import { UserContext } from '../../Contexts/UserContext' interface ProfileCreatePageProps { navigation: DrawerNavigationHelpers; @@ -13,7 +13,7 @@ interface ProfileCreatePageProps { export const ProfileCreatePage: React.FC = ({navigation}) => { const { t } = useTranslation('common') - const { setPrivateKey } = useContext(RelayPoolContext) + const { setPrivateKey } = useContext(UserContext) const [inputValue, setInputValue] = useState() const [copied, setCopied] = useState(false) diff --git a/frontend/Pages/ProfileLoadPage/index.tsx b/frontend/Pages/ProfileLoadPage/index.tsx index cacc96f..9101e87 100644 --- a/frontend/Pages/ProfileLoadPage/index.tsx +++ b/frontend/Pages/ProfileLoadPage/index.tsx @@ -2,7 +2,8 @@ import React, { useContext, useEffect, useState } from 'react' import { RelayPoolContext } from '../../Contexts/RelayPoolContext' import { EventKind } from '../../lib/nostr/Events' import { AppContext } from '../../Contexts/AppContext' -import { getUser, getUsers, User } from '../../Functions/DatabaseFunctions/Users' +import { UserContext } from '../../Contexts/UserContext' +import { getUsers, User } from '../../Functions/DatabaseFunctions/Users' import { useTranslation } from 'react-i18next' import moment from 'moment' import { StyleSheet, View } from 'react-native' @@ -12,16 +13,23 @@ import { navigate } from '../../lib/Navigation' export const ProfileLoadPage: React.FC = () => { const { loadingDb, database } = useContext(AppContext) - const { publicKey, relayPool, lastEventId, loadingRelayPool } = useContext(RelayPoolContext) + const { relayPool, lastEventId, loadingRelayPool } = useContext(RelayPoolContext) + const { publicKey, reloadUser, user } = useContext(UserContext) const { t } = useTranslation('common') const [profileFound, setProfileFound] = useState(false) - const [contactsCount, setContactsCount] = useState() + const [contactsCount, setContactsCount] = useState(0) useEffect(() => { if (!loadingRelayPool && !loadingDb && publicKey) { relayPool?.subscribe('loading-meta', [ { - kinds: [EventKind.petNames, EventKind.meta], + kinds: [EventKind.meta], + authors: [publicKey], + } + ]) + relayPool?.subscribe('loading-pets', [ + { + kinds: [EventKind.petNames], authors: [publicKey], }, { @@ -34,14 +42,19 @@ export const ProfileLoadPage: React.FC = () => { useEffect(() => { loadPets() - loadProfile() + reloadUser() }, [lastEventId]) + useEffect(() => { + if (user) setProfileFound(true) + }, [user]) + const loadPets: () => void = () => { - if (database) { + if (database && publicKey) { getUsers(database, { contacts: true }).then((results) => { - setContactsCount(results.length) - if (publicKey && results && results.length > 0) { + if (results && results.length > 0) { + reloadUser() + setContactsCount(results.length) const authors = [...results.map((user: User) => user.id), publicKey] relayPool?.subscribe('loading-notes', [ { @@ -55,16 +68,6 @@ export const ProfileLoadPage: React.FC = () => { } } - const loadProfile: () => void = () => { - if (database && publicKey) { - getUser(publicKey, database).then((result) => { - if (result) { - setProfileFound(true) - } - }) - } - } - return ( diff --git a/frontend/Pages/ProfilePage/index.tsx b/frontend/Pages/ProfilePage/index.tsx index 7954743..4b30a3c 100644 --- a/frontend/Pages/ProfilePage/index.tsx +++ b/frontend/Pages/ProfilePage/index.tsx @@ -1,391 +1,17 @@ -import { Button, Card, Layout, Spinner, Text, TopNavigation, useTheme } from '@ui-kitten/components' -import React, { useCallback, useContext, useEffect, useState } from 'react' -import { - Clipboard, - NativeScrollEvent, - NativeSyntheticEvent, - RefreshControl, - ScrollView, - StyleSheet, - TouchableOpacity, -} from 'react-native' -import { AppContext } from '../../Contexts/AppContext' -import { getNotes, Note } from '../../Functions/DatabaseFunctions/Notes' -import NoteCard from '../../Components/NoteCard' -import { RelayPoolContext } from '../../Contexts/RelayPoolContext' -import { getUser, User, updateUserContact } from '../../Functions/DatabaseFunctions/Users' -import { EventKind } from '../../lib/nostr/Events' -import Icon from 'react-native-vector-icons/FontAwesome5' -import { formatPubKey, populatePets } from '../../Functions/RelayFunctions/Users' -import { getReplyEventId } from '../../Functions/RelayFunctions/Events' -import Loading from '../../Components/Loading' -import { handleInfinityScroll } from '../../Functions/NativeFunctions' -import Avatar from '../../Components/Avatar' -import { RelayFilters } from '../../lib/nostr/RelayPool/intex' -import { t } from 'i18next' -import TextContent from '../../Components/TextContent' -import LnPayment from '../../Components/LnPayment' +import React from 'react' +import { StyleSheet, View } from 'react-native' export const ProfilePage: React.FC = () => { - const { database, page, goToPage, goBack } = useContext(AppContext) - const { publicKey, lastEventId, relayPool } = useContext(RelayPoolContext) - const theme = useTheme() - const initialPageSize = 10 - const [notes, setNotes] = useState() - const [user, setUser] = useState() - const [pageSize, setPageSize] = useState(initialPageSize) - const [isContact, setIsContact] = useState() - const [refreshing, setRefreshing] = useState(false) - const [openPayment, setOpenPayment] = useState(false) - const [firstLoad, setFirstLoad] = useState(true) - const breadcrump = page.split('%') - const userId = breadcrump[breadcrump.length - 1].split('#')[1] ?? publicKey - const username = user?.name === '' ? formatPubKey(user.id) : user?.name - - useEffect(() => { - setRefreshing(true) - setNotes(undefined) - setUser(undefined) - - loadUser() - loadNotes() - subscribeProfile() - subscribeNotes() - setFirstLoad(false) - }, [page]) - - useEffect(() => { - if (notes && !firstLoad) { - loadUser() - loadNotes() - } - }, [lastEventId]) - - useEffect(() => { - if (pageSize > initialPageSize && !firstLoad) { - loadUser() - loadNotes() - subscribeNotes(true) - } - }, [pageSize]) - - const loadUser: () => void = () => { - if (database) { - getUser(userId, database).then((result) => { - if (result) { - setUser(result) - setIsContact(result?.contact) - } - }) - } - } - - const loadNotes: (past?: boolean) => void = () => { - if (database) { - getNotes(database, { filters: { pubkey: userId }, limit: pageSize }).then((results) => { - setNotes(results) - setRefreshing(false) - relayPool?.subscribe('answers-profile', [ - { - kinds: [EventKind.reaction], - '#e': results.map((note) => note.id ?? ''), - }, - ]) - }) - } - } - - const subscribeNotes: (past?: boolean) => void = (past) => { - if (!database) return - - const message: RelayFilters = { - kinds: [EventKind.textNote, EventKind.recommendServer], - authors: [userId], - limit: pageSize, - } - relayPool?.subscribe('main-profile', [message]) - } - - const subscribeProfile: () => Promise = async () => { - relayPool?.subscribe('user-profile', [ - { - kinds: [EventKind.meta, EventKind.petNames], - authors: [userId], - }, - ]) - } - - const onRefresh = useCallback(() => { - setRefreshing(true) - relayPool?.unsubscribeAll() - loadUser() - loadNotes() - subscribeProfile() - subscribeNotes() - }, []) - - const removeAuthor: () => void = () => { - if (relayPool && database && publicKey) { - updateUserContact(userId, database, false).then(() => { - populatePets(relayPool, database, publicKey) - setIsContact(false) - }) - } - } - - const addAuthor: () => void = () => { - if (relayPool && database && publicKey) { - updateUserContact(userId, database, true).then(() => { - populatePets(relayPool, database, publicKey) - setIsContact(true) - }) - } - } - - const renderOptions: () => JSX.Element = () => { - const payment = user?.lnurl ? ( - - ) : ( - <> - ) - if (publicKey === userId) { - return ( - <> - {payment} - - - ) - return ( - <> - - {!user && userId === publicKey ? createProfile : profile} - - {notes && notes.length > 0 ? ( - } - > - {notes.map((note) => itemCard(note))} - {notes.length >= 10 && ( - - - - )} - - ) : ( - - )} - - - {publicKey === userId && ( - goToPage('contacts')} - > - - - )} - + + ) } +const styles = StyleSheet.create({ + container: { + padding: 16, + }, +}) + export default ProfilePage diff --git a/frontend/Pages/ProfilePageOld/index.tsx b/frontend/Pages/ProfilePageOld/index.tsx new file mode 100644 index 0000000..759c8f4 --- /dev/null +++ b/frontend/Pages/ProfilePageOld/index.tsx @@ -0,0 +1,342 @@ +import React, { useCallback, useContext, useEffect, useState } from 'react' +import { + Clipboard, + NativeScrollEvent, + NativeSyntheticEvent, + RefreshControl, + ScrollView, + StyleSheet, + TouchableOpacity, +} from 'react-native' +import { AppContext } from '../../Contexts/AppContext' +import { getNotes, Note } from '../../Functions/DatabaseFunctions/Notes' +import NoteCard from '../../Components/NoteCard' +import { RelayPoolContext } from '../../Contexts/RelayPoolContext' +import { getUser, User, updateUserContact } from '../../Functions/DatabaseFunctions/Users' +import { EventKind } from '../../lib/nostr/Events' +import Icon from 'react-native-vector-icons/FontAwesome5' +import { formatPubKey, populatePets } from '../../Functions/RelayFunctions/Users' +import { getReplyEventId } from '../../Functions/RelayFunctions/Events' +import { handleInfinityScroll } from '../../Functions/NativeFunctions' +import Avatar from '../../Components/Avatar' +import { RelayFilters } from '../../lib/nostr/RelayPool/intex' +import { t } from 'i18next' +import TextContent from '../../Components/TextContent' +import LnPayment from '../../Components/LnPayment' + +export const ProfilePage: React.FC = () => { + const { database, page, goToPage, goBack } = useContext(AppContext) + const { publicKey, lastEventId, relayPool } = useContext(RelayPoolContext) + const theme = useTheme() + const initialPageSize = 10 + const [notes, setNotes] = useState() + const [user, setUser] = useState() + const [pageSize, setPageSize] = useState(initialPageSize) + const [isContact, setIsContact] = useState() + const [refreshing, setRefreshing] = useState(false) + const [openPayment, setOpenPayment] = useState(false) + const [firstLoad, setFirstLoad] = useState(true) + const breadcrump = page.split('%') + const userId = breadcrump[breadcrump.length - 1].split('#')[1] ?? publicKey + const username = user?.name === '' ? formatPubKey(user.id) : user?.name + + useEffect(() => { + setRefreshing(true) + setNotes(undefined) + setUser(undefined) + + loadUser() + loadNotes() + subscribeProfile() + subscribeNotes() + setFirstLoad(false) + }, [page]) + + useEffect(() => { + if (notes && !firstLoad) { + loadUser() + loadNotes() + } + }, [lastEventId]) + + useEffect(() => { + if (pageSize > initialPageSize && !firstLoad) { + loadUser() + loadNotes() + subscribeNotes(true) + } + }, [pageSize]) + + const loadUser: () => void = () => { + if (database) { + getUser(userId, database).then((result) => { + if (result) { + setUser(result) + setIsContact(result?.contact) + } + }) + } + } + + const loadNotes: (past?: boolean) => void = () => { + if (database) { + getNotes(database, { filters: { pubkey: userId }, limit: pageSize }).then((results) => { + setNotes(results) + setRefreshing(false) + relayPool?.subscribe('answers-profile', [ + { + kinds: [EventKind.reaction], + '#e': results.map((note) => note.id ?? ''), + }, + ]) + }) + } + } + + const subscribeNotes: (past?: boolean) => void = (past) => { + if (!database) return + + const message: RelayFilters = { + kinds: [EventKind.textNote, EventKind.recommendServer], + authors: [userId], + limit: pageSize, + } + relayPool?.subscribe('main-profile', [message]) + } + + const subscribeProfile: () => Promise = async () => { + relayPool?.subscribe('user-profile', [ + { + kinds: [EventKind.meta, EventKind.petNames], + authors: [userId], + }, + ]) + } + + const onRefresh = useCallback(() => { + setRefreshing(true) + relayPool?.unsubscribeAll() + loadUser() + loadNotes() + subscribeProfile() + subscribeNotes() + }, []) + + const removeAuthor: () => void = () => { + if (relayPool && database && publicKey) { + updateUserContact(userId, database, false).then(() => { + populatePets(relayPool, database, publicKey) + setIsContact(false) + }) + } + } + + const addAuthor: () => void = () => { + if (relayPool && database && publicKey) { + updateUserContact(userId, database, true).then(() => { + populatePets(relayPool, database, publicKey) + setIsContact(true) + }) + } + } + + const renderOptions: () => JSX.Element = () => { + const payment = user?.lnurl ? ( + // + <> + ) : ( + <> + ) + if (publicKey === userId) { + return ( + <> + {payment} + {/* + // + <> + ) + + return ( + <> + {/* + {!user && userId === publicKey ? createProfile : profile} + + {notes && notes.length > 0 ? ( + } + > + {notes.map((note) => itemCard(note))} + {notes.length >= 10 && ( + + + + )} + + ) : ( + + )} + + + {publicKey === userId && ( + goToPage('contacts')} + > + + + )} */} + + ) +} + +export default ProfilePage diff --git a/frontend/Pages/RelaysPage/index.tsx b/frontend/Pages/RelaysPage/index.tsx index 0cf1119..6dabec0 100644 --- a/frontend/Pages/RelaysPage/index.tsx +++ b/frontend/Pages/RelaysPage/index.tsx @@ -48,7 +48,7 @@ export const RelaysPage: React.FC = () => { const onPressAddRelay: () => void = () => { if (REGEX_SOCKET_LINK.test(addRelayInput)) { bottomSheetAddRef.current?.close() - + setAddRelayInput(defaultRelayInput) } else { bottomSheetAddRef.current?.close() @@ -88,6 +88,16 @@ export const RelaysPage: React.FC = () => { /> ) + const rbSheetCustomStyles = React.useMemo(() => { + return { + container: { + ...styles.rbsheetContainer, + backgroundColor: theme.colors.background, + }, + draggableIcon: styles.rbsheetDraggableIcon, + } + }, []) + return ( @@ -109,18 +119,7 @@ export const RelaysPage: React.FC = () => { > {t(`relaysPage.${showNotification}`)} - + { ref={bottomSheetEditRef} closeOnDragDown={true} height={260} - customStyles={{ - container: { - ...styles.rbsheetContainer, - backgroundColor: theme.colors.background, - }, - draggableIcon: styles.rbsheetDraggableIcon, - }} + customStyles={rbSheetCustomStyles} > @@ -179,7 +172,7 @@ export const RelaysPage: React.FC = () => { {t('relaysPage.copyRelay')} - + {selectedRelay?.url.split('wss://')[1]?.split('/')[0]} @@ -217,12 +210,12 @@ const styles = StyleSheet.create({ actionButton: { justifyContent: 'center', alignItems: 'center', - width: 80 + width: 80, }, divider: { marginBottom: 26, - marginTop: 26 - } + marginTop: 26, + }, }) export default RelaysPage diff --git a/frontend/index.tsx b/frontend/index.tsx index 07b3160..f6bef9e 100644 --- a/frontend/index.tsx +++ b/frontend/index.tsx @@ -1,7 +1,6 @@ import React from 'react' import { AppContextProvider } from './Contexts/AppContext' import { - InitialState, NavigationContainer, DefaultTheme as NavigationDefaultTheme, DarkTheme as NavigationDarkTheme, @@ -17,6 +16,7 @@ import { navigationRef } from './lib/Navigation' import HomeNavigator from './Pages/HomeNavigator' import MenuItems from './Components/MenuItems' import FeedNavigator from './Pages/FeedNavigator' +import { UserContextProvider } from './Contexts/UserContext' const DrawerNavigator = createDrawerNavigator() @@ -42,36 +42,38 @@ export const Frontend: React.FC = () => { - - - - {() => { - return ( - } - screenOptions={{ - drawerStyle: { - borderRadius: 28, - width: 296 - }, - }} - > - - - - ) - }} - - - + + + + + {() => { + return ( + } + screenOptions={{ + drawerStyle: { + borderRadius: 28, + width: 296 + }, + }} + > + + + + ) + }} + + + + diff --git a/frontend/lib/Navigation/index.ts b/frontend/lib/Navigation/index.ts index 636ed4f..0f905db 100644 --- a/frontend/lib/Navigation/index.ts +++ b/frontend/lib/Navigation/index.ts @@ -6,4 +6,10 @@ export const navigate: (name: string, params?: any) => void = (name, params ={}) if (navigationRef.isReady()) { navigationRef.navigate(name as never, params as never); } +} + +export const jumpTo: (name: string, params?: any) => void = (name, params ={}) => { + if (navigationRef.isReady()) { + navigationRef.jumpTo(name as never, params as never); + } } \ No newline at end of file diff --git a/package.json b/package.json index b0d3d68..4e3f491 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "react-native-multithreading": "^1.1.1", "react-native-paper": "^5.1.3", "react-native-parsed-text": "^0.0.22", + "react-native-qrcode-svg": "^6.1.2", "react-native-quick-sqlite": "^6.1.1", "react-native-raw-bottom-sheet": "^2.2.0", "react-native-reanimated": "^2.14.0", @@ -46,7 +47,7 @@ "react-native-screens": "^3.18.2", "react-native-securerandom": "^1.0.1", "react-native-sensitive-info": "^5.5.8", - "react-native-svg": "^13.5.0", + "react-native-svg": "^13.7.0", "react-native-vector-icons": "^9.2.0", "react-native-webp-format": "^1.1.2", "readable-stream": "^4.3.0", diff --git a/yarn.lock b/yarn.lock index b242db4..2cd0346 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3452,6 +3452,11 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" +dijkstrajs@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.2.tgz#2e48c0d3b825462afe75ab4ad5e829c8ece36257" + integrity sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -3549,6 +3554,11 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +encode-utf8@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda" + integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw== + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -6894,6 +6904,11 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +pngjs@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" + integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" @@ -7013,6 +7028,16 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +qrcode@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.1.tgz#0103f97317409f7bc91772ef30793a54cd59f0cb" + integrity sha512-nS8NJ1Z3md8uTjKtP+SGGhfqmTCs5flU/xR623oI0JX+Wepz9R8UrRVCTBTJm3qGw3rH6jJ6MUHjkDx15cxSSg== + dependencies: + dijkstrajs "^1.0.1" + encode-utf8 "^1.0.3" + pngjs "^5.0.0" + yargs "^15.3.1" + query-string@^7.1.3: version "7.1.3" resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328" @@ -7148,6 +7173,14 @@ react-native-parsed-text@^0.0.22: dependencies: prop-types "^15.7.x" +react-native-qrcode-svg@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/react-native-qrcode-svg/-/react-native-qrcode-svg-6.1.2.tgz#a7cb6c10199ab01418a7f7700ce17a6a014f544e" + integrity sha512-lMbbxoPVybXCp9SYm73Aj/0iZ9OlSZl2u+zpdbjgC4DYHBm9m9tDQxISNg1OPeR7AAzmyx8IV4JTFmk8G5R22g== + dependencies: + prop-types "^15.7.2" + qrcode "^1.5.0" + react-native-quick-sqlite@^6.1.1: version "6.1.1" resolved "https://registry.yarnpkg.com/react-native-quick-sqlite/-/react-native-quick-sqlite-6.1.1.tgz#ba35d0a4a919a5a0962306c3fee4dc46983b0839" @@ -7196,10 +7229,10 @@ react-native-sensitive-info@^5.5.8: resolved "https://registry.yarnpkg.com/react-native-sensitive-info/-/react-native-sensitive-info-5.5.8.tgz#6ebb67eed83d1c2867bd435630ef2c41eef204ed" integrity sha512-p99oaEW4QG1RdUNrkvd/c6Qdm856dQw/Rk81f9fA6Y3DlPs6ADNdU+jbPuTz3CcOUJwuKBDNenX6LR9KfmGFEg== -react-native-svg@^13.5.0: - version "13.6.0" - resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-13.6.0.tgz#46e95a44aabbd778db7c46d8a1047da376b28058" - integrity sha512-1wjHCMJ8siyZbDZ0MX5wM+Jr7YOkb6GADn4/Z+/u1UwJX8WfjarypxDF3UO1ugMHa+7qor39oY+URMcrgPpiww== +react-native-svg@^13.7.0: + version "13.7.0" + resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-13.7.0.tgz#be2ffb935e996762543dd7376bdc910722f7a43c" + integrity sha512-WR5CIURvee5cAfvMhmdoeOjh1SC8KdLq5u5eFsz4pbYzCtIFClGSkLnNgkMSDMVV5LV0qQa4jeIk75ieIBzaDA== dependencies: css-select "^5.1.0" css-tree "^1.1.3"