From b21067e4b20589e3bcbeec0927b70684fc54102a Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Mon, 20 Feb 2023 18:18:09 +0100 Subject: [PATCH 1/3] Long press zap --- frontend/Components/LnPayment/index.tsx | 66 ++++--------- frontend/Components/NoteCard/index.tsx | 48 +++++++++- frontend/Components/ProfileData/index.tsx | 7 +- frontend/Components/UploadImage/index.tsx | 6 ++ frontend/Contexts/AppContext.tsx | 9 ++ .../DatabaseFunctions/Groups/index.ts | 2 +- frontend/Functions/NativeFunctions/index.ts | 13 +++ .../ServicesFunctions/ZapInvoice/index.ts | 83 ++++++++++++++++ frontend/Locales/de.json | 11 ++- frontend/Locales/en.json | 11 ++- frontend/Locales/es.json | 11 ++- frontend/Locales/fr.json | 11 ++- frontend/Locales/ru.json | 11 ++- frontend/Locales/zhCn.json | 11 ++- frontend/Pages/ConfigPage/index.tsx | 95 ++++++++++++++++--- frontend/Pages/ConversationPage/index.tsx | 9 +- frontend/Pages/ConversationsFeed/index.tsx | 5 +- frontend/Pages/GroupPage/index.tsx | 15 ++- frontend/Pages/SendPage/index.tsx | 1 + frontend/lib/nostr/RelayPool/intex.ts | 50 ++++++++-- 20 files changed, 376 insertions(+), 99 deletions(-) create mode 100644 frontend/Functions/ServicesFunctions/ZapInvoice/index.ts diff --git a/frontend/Components/LnPayment/index.tsx b/frontend/Components/LnPayment/index.tsx index 58578a4..23fbca5 100644 --- a/frontend/Components/LnPayment/index.tsx +++ b/frontend/Components/LnPayment/index.tsx @@ -16,11 +16,8 @@ import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityI import { RelayPoolContext } from '../../Contexts/RelayPoolContext' import { Kind } from 'nostr-tools' import { getUnixTime } from 'date-fns' -import { Event, signEvent } from '../../lib/nostr/Events' -import { getRelays, Relay } from '../../Functions/DatabaseFunctions/Relays' import { UserContext } from '../../Contexts/UserContext' -import { requestInvoiceWithServiceParams, requestPayServiceParams } from 'lnurl-pay' -import axios from 'axios' +import { lightningInvoice } from '../../Functions/ServicesFunctions/ZapInvoice' interface LnPaymentProps { open: boolean @@ -86,57 +83,26 @@ export const LnPayment: React.FC = ({ open, setOpen, note, user setIsZap(zap) const lud = lnAddress && lnAddress !== '' ? lnAddress : lnurl - if (lud && lud !== '' && monto !== '') { + if (lud && lud !== '' && monto !== '' && database && privateKey && publicKey && userId) { setLoading(true) - const tokens: number = parseInt(monto, 10) ?? 0 - let nostr: string - - if (zap && database && privateKey && publicKey && zapPubkey && userId) { - const relays: Relay[] = await getRelays(database) - const tags = [ - ['p', userId], - ['amount', (tokens * 1000).toString()], - ['relays', ...relays.map((relay) => relay.url)], - ] - if (note?.id) tags.push(['e', note.id]) - - const event: Event = { - content: comment, - created_at: getUnixTime(new Date()), - kind: 9734, - pubkey: publicKey, - tags, - } - const signedEvent = await signEvent(event, privateKey) - nostr = JSON.stringify(signedEvent) - } - - const serviceParams = await requestPayServiceParams({ lnUrlOrAddress: lud }) - - requestInvoiceWithServiceParams({ - params: serviceParams, - lnUrlOrAddress: lud, - tokens, + lightningInvoice( + database, + lud, + parseInt(monto, 10), + privateKey, + publicKey, + userId, + zap, + zapPubkey, comment, - fetchGet: async ({ url, params }) => { - if (params && nostr && serviceParams.rawData.allowsNostr) { - params.nostr = nostr - } - const response = await axios.get(url, { - params, - }) - console.log(response) - return response.data - }, - }) - .then((action) => { - if (action.hasValidAmount && action.invoice) { - setInvoice(action.invoice) - } + note?.id, + ) + .then((invoice) => { + if (invoice) setInvoice(invoice) setLoading(false) }) - .catch((e) => { + .catch(() => { setLoading(false) }) } diff --git a/frontend/Components/NoteCard/index.tsx b/frontend/Components/NoteCard/index.tsx index 64b1053..2cf3643 100644 --- a/frontend/Components/NoteCard/index.tsx +++ b/frontend/Components/NoteCard/index.tsx @@ -41,6 +41,9 @@ import { SvgXml } from 'react-native-svg' import { reactionIcon } from '../../Constants/Theme' import LnPayment from '../LnPayment' import { getZapsAmount } from '../../Functions/DatabaseFunctions/Zaps' +import { lightningInvoice } from '../../Functions/ServicesFunctions/ZapInvoice' +import LnPreview from '../LnPreview' +import { getNpub } from '../../lib/nostr/Nip19' interface NoteCardProps { note?: Note @@ -72,7 +75,8 @@ export const NoteCard: React.FC = ({ const theme = useTheme() const { publicKey, privateKey } = React.useContext(UserContext) const { relayPool, lastEventId, setDisplayrelayDrawer } = useContext(RelayPoolContext) - const { database, showSensitive, setDisplayUserDrawer, relayColouring } = useContext(AppContext) + const { database, showSensitive, setDisplayUserDrawer, relayColouring, longPressZap } = + useContext(AppContext) const [relayAdded, setRelayAdded] = useState(false) const [positiveReactions, setPositiveReactions] = useState(0) const [negativeReactions, setNegativeReactions] = useState(0) @@ -87,6 +91,8 @@ export const NoteCard: React.FC = ({ const [repost, setRepost] = useState() const [openLn, setOpenLn] = React.useState(false) const [showReactions, setShowReactions] = React.useState(false) + const [loadingZap, setLoadingZap] = React.useState(false) + const [zapInvoice, setZapInvoice] = React.useState() useEffect(() => { if (database && publicKey && note?.id) { @@ -340,6 +346,41 @@ export const NoteCard: React.FC = ({ ) + const generateZapInvoice: () => void = () => { + const lud = note?.ln_address && note?.ln_address !== '' ? note?.ln_address : note?.lnurl + + if ( + lud && + lud !== '' && + longPressZap && + database && + privateKey && + publicKey && + note?.pubkey + ) { + setLoadingZap(true) + lightningInvoice( + database, + lud, + longPressZap, + privateKey, + publicKey, + note?.pubkey, + true, + note?.zap_pubkey, + `Nostr: ${formatPubKey(getNpub(note?.id))}`, + note?.id, + ) + .then((invoice) => { + if (invoice) setZapInvoice(invoice) + setLoadingZap(false) + }) + .catch(() => { + setLoadingZap(false) + }) + } + } + const reactionsCount: () => number = () => { if (userDownvoted) return negativeReactions if (userUpvoted) return positiveReactions @@ -432,10 +473,15 @@ export const NoteCard: React.FC = ({ /> )} onPress={() => setOpenLn(true)} + onLongPress={ + longPressZap ? generateZapInvoice : undefined + } + loading={loadingZap} > {note.zap_pubkey?.length > 0 ? formatBigNumber(zapsAmount) : ''} {openLn && } + {zapInvoice && } )} diff --git a/frontend/Components/ProfileData/index.tsx b/frontend/Components/ProfileData/index.tsx index 5c9016b..efaddf4 100644 --- a/frontend/Components/ProfileData/index.tsx +++ b/frontend/Components/ProfileData/index.tsx @@ -4,8 +4,8 @@ import { Text, useTheme } from 'react-native-paper' import { getNip05Domain, usernamePubKey } from '../../Functions/RelayFunctions/Users' import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons' import NostrosAvatar from '../NostrosAvatar' -import { fromUnixTime, formatDistance } from 'date-fns' import { getNpub } from '../../lib/nostr/Nip19' +import { formatDate } from '../../Functions/NativeFunctions' interface ProfileCardProps { username?: string @@ -32,10 +32,7 @@ export const ProfileData: React.FC = ({ }) => { const theme = useTheme() const nPub = React.useMemo(() => (publicKey ? getNpub(publicKey) : ''), [publicKey]) - const date = React.useMemo( - () => (timestamp ? formatDistance(fromUnixTime(timestamp), new Date()) : null), - [timestamp], - ) + const date = React.useMemo(() => formatDate(timestamp), [timestamp]) return ( diff --git a/frontend/Components/UploadImage/index.tsx b/frontend/Components/UploadImage/index.tsx index 7477ad7..9b64d10 100644 --- a/frontend/Components/UploadImage/index.tsx +++ b/frontend/Components/UploadImage/index.tsx @@ -13,6 +13,7 @@ interface UploadImageProps { setImageUri: (uri: string) => void uploadingFile: boolean setUploadingFile: (uploading: boolean) => void + onError: () => void } export const UploadImage: React.FC = ({ @@ -21,6 +22,7 @@ export const UploadImage: React.FC = ({ setImageUri, uploadingFile, setUploadingFile, + onError, }) => { const { getImageHostingService } = useContext(AppContext) const theme = useTheme() @@ -46,10 +48,12 @@ export const UploadImage: React.FC = ({ setUploadingFile(false) bottomSheetImageRef.current?.open() } else { + onError() setUploadingFile(false) setShowNotification('imageUploadErro') } } else { + onError() setUploadingFile(false) setShowNotification('imageUploadErro') } @@ -72,6 +76,7 @@ export const UploadImage: React.FC = ({ bottomSheetImageRef.current?.close() setUploadingFile(false) setShowNotification('imageUploadErro') + onError() }) } } @@ -122,6 +127,7 @@ export const UploadImage: React.FC = ({ onPress={() => { bottomSheetImageRef.current?.close() setImageUpload(undefined) + onError() }} > {t('uploadImage.cancel')} diff --git a/frontend/Contexts/AppContext.tsx b/frontend/Contexts/AppContext.tsx index a49fb4c..8244c66 100644 --- a/frontend/Contexts/AppContext.tsx +++ b/frontend/Contexts/AppContext.tsx @@ -37,6 +37,8 @@ export interface AppContextProps { setDisplayUserDrawer: (displayUserDrawer: string | undefined) => void refreshBottomBarAt?: number setRefreshBottomBarAt: (refreshBottomBarAt: number) => void + longPressZap?: number | undefined + setLongPressZap: (longPressZap: number | undefined) => void pushedTab?: string setPushedTab: (pushedTab: string) => void } @@ -76,6 +78,8 @@ export const initialAppContext: AppContextProps = { getSatoshiSymbol: () => <>, setClipboardNip21: () => {}, setDisplayUserDrawer: () => {}, + longPressZap: undefined, + setLongPressZap: () => {}, } export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.Element => { @@ -92,6 +96,7 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E const [imageHostingService, setImageHostingService] = React.useState( initialAppContext.imageHostingService, ) + const [longPressZap, setLongPressZap] = React.useState() const [notificationSeenAt, setNotificationSeenAt] = React.useState(0) const [refreshBottomBarAt, setRefreshBottomBarAt] = React.useState(0) const [satoshi, setSatoshi] = React.useState<'kebab' | 'sats'>(initialAppContext.satoshi) @@ -148,6 +153,7 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E config.image_hosting_service ?? initialAppContext.imageHostingService, ) setLanguage(config.language ?? initialAppContext.language) + setLongPressZap(config.long_press_zap ?? initialAppContext.longPressZap) } else { const config: Config = { show_public_images: initialAppContext.showPublicImages, @@ -158,6 +164,7 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E last_pets_at: 0, language: initialAppContext.language, relay_coloruring: initialAppContext.relayColouring, + long_press_zap: initialAppContext.longPressZap, } SInfo.setItem('config', JSON.stringify(config), {}) } @@ -229,6 +236,8 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E setRefreshBottomBarAt, pushedTab, setPushedTab, + longPressZap, + setLongPressZap, }} > {children} diff --git a/frontend/Functions/DatabaseFunctions/Groups/index.ts b/frontend/Functions/DatabaseFunctions/Groups/index.ts index d64617d..413eabf 100644 --- a/frontend/Functions/DatabaseFunctions/Groups/index.ts +++ b/frontend/Functions/DatabaseFunctions/Groups/index.ts @@ -177,7 +177,7 @@ export const getUserGroupMessagesCount: ( SELECT COUNT(*) FROM nostros_group_messages - WHERE (user_mentioned != NULL AND user_mentioned = 1) + WHERE user_mentioned = 1 AND (read = NULL OR read = 0) ` diff --git a/frontend/Functions/NativeFunctions/index.ts b/frontend/Functions/NativeFunctions/index.ts index 50ff327..9ff59d9 100644 --- a/frontend/Functions/NativeFunctions/index.ts +++ b/frontend/Functions/NativeFunctions/index.ts @@ -1,3 +1,5 @@ +import { endOfYesterday, format, formatDistanceToNow, fromUnixTime, isBefore } from 'date-fns' + export const handleInfinityScroll: (event: any) => boolean = (event) => { const mHeight = event.nativeEvent.layoutMeasurement.height const cSize = event.nativeEvent.contentSize.height @@ -81,6 +83,17 @@ export const validNip21: (string: string | undefined) => boolean = (string) => { } } +export const formatDate: (unix: number | undefined) => string = (unix) => { + if (!unix) return '' + + const date = fromUnixTime(unix) + if (isBefore(date, endOfYesterday())) { + return formatDistanceToNow(fromUnixTime(unix), { addSuffix: true }) + } else { + return format(date, 'HH:mm') + } +} + export const formatBigNumber: (num: number | undefined) => string = (num) => { if (num === undefined) return '' diff --git a/frontend/Functions/ServicesFunctions/ZapInvoice/index.ts b/frontend/Functions/ServicesFunctions/ZapInvoice/index.ts new file mode 100644 index 0000000..ec59756 --- /dev/null +++ b/frontend/Functions/ServicesFunctions/ZapInvoice/index.ts @@ -0,0 +1,83 @@ +// Thanks to v0l/snort for the nice code! +// https://github.com/v0l/snort/blob/39fbe3b10f94b7542df01fb085e4f164aab15fca/src/Feed/VoidUpload.ts + +import { QuickSQLiteConnection } from 'react-native-quick-sqlite' +import { getRelays, Relay } from '../../DatabaseFunctions/Relays' +import { getUnixTime } from 'date-fns' +import { Event, signEvent } from '../../../lib/nostr/Events' +import { requestInvoiceWithServiceParams, requestPayServiceParams } from 'lnurl-pay' +import axios from 'axios' + +export const lightningInvoice: ( + database: QuickSQLiteConnection, + lud: string, + tokens: number, + privateKey: string, + publicKey: string, + userId: string, + zap?: boolean, + zapPubkey?: string, + comment?: string, + noteId?: string, +) => Promise = async ( + database, + lud, + tokens, + privateKey, + publicKey, + userId, + zap, + zapPubkey, + comment, + noteId, +) => { + let nostr: string + + if (zap && database && privateKey && publicKey && zapPubkey && userId) { + const relays: Relay[] = await getRelays(database) + const tags = [ + ['p', userId], + ['amount', (tokens * 1000).toString()], + ['relays', ...relays.map((relay) => relay.url)], + ] + if (noteId) tags.push(['e', noteId]) + + const event: Event = { + content: comment, + created_at: getUnixTime(new Date()), + kind: 9734, + pubkey: publicKey, + tags, + } + const signedEvent = await signEvent(event, privateKey) + nostr = JSON.stringify(signedEvent) + } + + const serviceParams = await requestPayServiceParams({ lnUrlOrAddress: lud }) + + return await new Promise((resolve, reject) => { + requestInvoiceWithServiceParams({ + params: serviceParams, + lnUrlOrAddress: lud, + tokens, + comment, + fetchGet: async ({ url, params }) => { + if (params && nostr && serviceParams.rawData.allowsNostr) { + params.nostr = nostr + } + const response = await axios.get(url, { + params, + }) + return response.data + }, + }) + .then((action) => { + if (action.hasValidAmount && action.invoice) { + resolve(action.invoice) + } + }) + .catch((e) => { + reject(new Error()) + }) + }) +} diff --git a/frontend/Locales/de.json b/frontend/Locales/de.json index 4f19b46..6854466 100644 --- a/frontend/Locales/de.json +++ b/frontend/Locales/de.json @@ -82,7 +82,16 @@ "imageHostingService": "Bilder Hosting Service", "random": "zufällig", "language": "Sprache", - "relayColoruring": "Relays farblich darstellen" + "relayColoruring": "Relays farblich darstellen", + "app": "App", + "feed": "Feed", + "zaps": "Zaps", + "disabled": "Disabled", + "longPressZap": "Long press Zaps", + "longPressZapDescription": "Define a default amount to automatically generate invoices after a long press on the Zap button.", + "defaultZapAmount": "Defaul Zap amount", + "update": "Update", + "disable": "Disable" }, "noteCard": { "answering": "{{pubkey}} antworten", diff --git a/frontend/Locales/en.json b/frontend/Locales/en.json index 1f66254..776230b 100644 --- a/frontend/Locales/en.json +++ b/frontend/Locales/en.json @@ -82,7 +82,16 @@ "imageHostingService": "Image hosting service", "random": "Random", "language": "Language", - "relayColoruring": "Relay colouring" + "relayColoruring": "Relay colouring", + "app": "App", + "feed": "Feed", + "zaps": "Zaps", + "disabled": "Disabled", + "longPressZap": "Long press Zaps", + "longPressZapDescription": "Define a default amount to automatically generate invoices after a long press on the Zap button.", + "defaultZapAmount": "Defaul Zap amount", + "update": "Update", + "disable": "Disable" }, "noteCard": { "answering": "Answer to {{pubkey}}", diff --git a/frontend/Locales/es.json b/frontend/Locales/es.json index f4b3a3b..c0f30f9 100644 --- a/frontend/Locales/es.json +++ b/frontend/Locales/es.json @@ -103,7 +103,16 @@ "imageHostingService": "Servicio de subida de imágenes", "random": "Aleatorio", "language": "Idioma", - "relayColoruring": "Coloreado de relays" + "relayColoruring": "Coloreado de relays", + "app": "Aplicación", + "feed": "Feed", + "zaps": "Zaps", + "disabled": "Desabilitado", + "longPressZap": "Mantener pulsado para Zap", + "longPressZapDescription": "Define una cantidad por defecto para generar facturas LN tras mantener pulsado el botón de Zap.", + "defaultZapAmount": "Cantidad por defecto", + "update": "Actualizar", + "disable": "Desabilitar" }, "noteCard": { "answering": "Responder a {{pubkey}}", diff --git a/frontend/Locales/fr.json b/frontend/Locales/fr.json index e29949f..89ddc35 100644 --- a/frontend/Locales/fr.json +++ b/frontend/Locales/fr.json @@ -110,7 +110,16 @@ "satoshi": "Symbole de satoshi", "imageHostingService": "Service d'hébergement d'images", "language": "Langue", - "relayColoruring": "Relay coloruring" + "relayColoruring": "Relay coloruring", + "app": "App", + "feed": "Feed", + "zaps": "Zaps", + "disabled": "Disabled", + "longPressZap": "Long press Zaps", + "longPressZapDescription": "Define a default amount to automatically generate invoices after a long press on the Zap button.", + "defaultZapAmount": "Defaul Zap amount", + "update": "Update", + "disable": "Disable" }, "noteCard": { "answering": "Répondre à {{pubkey}}", diff --git a/frontend/Locales/ru.json b/frontend/Locales/ru.json index 5ca8088..9dd667c 100644 --- a/frontend/Locales/ru.json +++ b/frontend/Locales/ru.json @@ -103,7 +103,16 @@ "satoshi": "Satoshi symbol", "random": "Random", "language": "Язык", - "relayColoruring": "Relay coloruring" + "relayColoruring": "Relay coloruring", + "app": "App", + "feed": "Feed", + "zaps": "Zaps", + "disabled": "Disabled", + "longPressZap": "Long press Zaps", + "longPressZapDescription": "Define a default amount to automatically generate invoices after a long press on the Zap button.", + "defaultZapAmount": "Defaul Zap amount", + "update": "Update", + "disable": "Disable" }, "noteCard": { "answering": "Ответить {{pubkey}}", diff --git a/frontend/Locales/zhCn.json b/frontend/Locales/zhCn.json index 429c977..d297a25 100644 --- a/frontend/Locales/zhCn.json +++ b/frontend/Locales/zhCn.json @@ -102,7 +102,16 @@ "imageHostingService": "图片托管服务", "random": "随机", "language": "语言", - "relayColoruring": "颜色标示中继状态" + "relayColoruring": "颜色标示中继状态", + "app": "App", + "feed": "Feed", + "zaps": "Zaps", + "disabled": "Disabled", + "longPressZap": "Long press Zaps", + "longPressZapDescription": "Define a default amount to automatically generate invoices after a long press on the Zap button.", + "defaultZapAmount": "Defaul Zap amount", + "update": "Update", + "disable": "Disable" }, "noteCard": { "answering": "回复 {{pubkey}}", diff --git a/frontend/Pages/ConfigPage/index.tsx b/frontend/Pages/ConfigPage/index.tsx index 832e3d6..1be8a9c 100644 --- a/frontend/Pages/ConfigPage/index.tsx +++ b/frontend/Pages/ConfigPage/index.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' -import { FlatList, StyleSheet, Text } from 'react-native' -import { Divider, List, Switch, useTheme } from 'react-native-paper' +import { FlatList, StyleSheet } from 'react-native' +import { Button, Divider, List, Switch, Text, TextInput, useTheme } from 'react-native-paper' import SInfo from 'react-native-sensitive-info' import RBSheet from 'react-native-raw-bottom-sheet' import { AppContext } from '../../Contexts/AppContext' @@ -16,6 +16,7 @@ export interface Config { image_hosting_service: string language: string relay_coloruring: boolean + long_press_zap: number | undefined } export const ConfigPage: React.FC = () => { @@ -35,10 +36,14 @@ export const ConfigPage: React.FC = () => { setLanguage, relayColouring, setRelayColouring, + longPressZap, + setLongPressZap, } = React.useContext(AppContext) const bottomSheetSatoshiRef = React.useRef(null) const bottomSheetImageHostingRef = React.useRef(null) const bottomSheetLanguageRef = React.useRef(null) + const bottomSheetLongPressZapRef = React.useRef(null) + const [zapAmount, setZapAmount] = React.useState(longPressZap?.toString()) React.useEffect(() => {}, [showPublicImages, showSensitive, satoshi]) @@ -126,6 +131,25 @@ export const ConfigPage: React.FC = () => { return ( <> + + + bottomSheetLanguageRef.current?.open()} + right={() => {t(`language.${language}`)}} + /> + bottomSheetImageHostingRef.current?.open()} + right={() => ( + + {imageHostingServices[imageHostingService]?.uri ?? + t(`configPage.${imageHostingService}`)} + + )} + /> + + ( @@ -174,23 +198,19 @@ export const ConfigPage: React.FC = () => { /> )} /> - bottomSheetLanguageRef.current?.open()} - right={() => {t(`language.${language}`)}} - /> + + bottomSheetSatoshiRef.current?.open()} right={() => getSatoshiSymbol(25)} /> bottomSheetImageHostingRef.current?.open()} + title={t('configPage.longPressZap')} + onPress={() => bottomSheetLongPressZapRef.current?.open()} right={() => ( - {imageHostingServices[imageHostingService]?.uri ?? - t(`configPage.${imageHostingService}`)} + {longPressZap ?? t('configPage.disabled')} )} /> @@ -225,6 +245,56 @@ export const ConfigPage: React.FC = () => { ItemSeparatorComponent={Divider} /> + + {t('configPage.longPressZap')} + + {t('configPage.longPressZapDescription')} + + + + + ) } @@ -234,6 +304,9 @@ const styles = StyleSheet.create({ fontFamily: 'Satoshi-Symbol', fontSize: 25, }, + input: { + marginTop: 16, + }, }) export default ConfigPage diff --git a/frontend/Pages/ConversationPage/index.tsx b/frontend/Pages/ConversationPage/index.tsx index d4ab5d1..6ee5f0e 100644 --- a/frontend/Pages/ConversationPage/index.tsx +++ b/frontend/Pages/ConversationPage/index.tsx @@ -20,7 +20,7 @@ import { import { getUser, User } from '../../Functions/DatabaseFunctions/Users' import { useTranslation } from 'react-i18next' import { username, usernamePubKey, usersToTags } from '../../Functions/RelayFunctions/Users' -import { getUnixTime, formatDistance, fromUnixTime } from 'date-fns' +import { getUnixTime } from 'date-fns' import TextContent from '../../Components/TextContent' import { encrypt, decrypt } from '../../lib/nostr/Nip04' import { @@ -36,7 +36,7 @@ import { UserContext } from '../../Contexts/UserContext' import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons' import { useFocusEffect } from '@react-navigation/native' import { Kind } from 'nostr-tools' -import { handleInfinityScroll } from '../../Functions/NativeFunctions' +import { formatDate, handleInfinityScroll } from '../../Functions/NativeFunctions' import NostrosAvatar from '../../Components/NostrosAvatar' import UploadImage from '../../Components/UploadImage' import { Swipeable } from 'react-native-gesture-handler' @@ -246,9 +246,7 @@ export const ConversationPage: React.FC = ({ route }) => /> )} - - {message?.created_at && formatDistance(fromUnixTime(message.created_at), new Date())} - + {formatDate(message?.created_at)} {message ? ( @@ -456,6 +454,7 @@ export const ConversationPage: React.FC = ({ route }) => setInput((prev) => `${prev} ${imageUri}`) setStartUpload(false) }} + onError={() => setStartUpload(false)} uploadingFile={uploadingFile} setUploadingFile={setUploadingFile} /> diff --git a/frontend/Pages/ConversationsFeed/index.tsx b/frontend/Pages/ConversationsFeed/index.tsx index f965d75..7b6b121 100644 --- a/frontend/Pages/ConversationsFeed/index.tsx +++ b/frontend/Pages/ConversationsFeed/index.tsx @@ -38,8 +38,7 @@ import { useTranslation } from 'react-i18next' import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons' import { useFocusEffect } from '@react-navigation/native' import ProfileData from '../../Components/ProfileData' -import { fromUnixTime, formatDistance } from 'date-fns' -import { handleInfinityScroll } from '../../Functions/NativeFunctions' +import { formatDate, handleInfinityScroll } from '../../Functions/NativeFunctions' export const ConversationsFeed: React.FC = () => { const initialPageSize = 14 @@ -148,7 +147,7 @@ export const ConversationsFeed: React.FC = () => { - {formatDistance(fromUnixTime(item.created_at), new Date())} + {formatDate(item?.created_at)} {item.pubkey !== publicKey && !item.read && } diff --git a/frontend/Pages/GroupPage/index.tsx b/frontend/Pages/GroupPage/index.tsx index 730b6a0..6035a6a 100644 --- a/frontend/Pages/GroupPage/index.tsx +++ b/frontend/Pages/GroupPage/index.tsx @@ -13,7 +13,7 @@ import { RelayPoolContext } from '../../Contexts/RelayPoolContext' import { Event } from '../../lib/nostr/Events' import { useTranslation } from 'react-i18next' import { formatPubKey, username, usernamePubKey } from '../../Functions/RelayFunctions/Users' -import { getUnixTime, formatDistance, fromUnixTime } from 'date-fns' +import { getUnixTime } from 'date-fns' import TextContent from '../../Components/TextContent' import { Card, @@ -28,7 +28,7 @@ import { UserContext } from '../../Contexts/UserContext' import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons' import { useFocusEffect } from '@react-navigation/native' import { Kind } from 'nostr-tools' -import { handleInfinityScroll } from '../../Functions/NativeFunctions' +import { formatDate, handleInfinityScroll } from '../../Functions/NativeFunctions' import NostrosAvatar from '../../Components/NostrosAvatar' import UploadImage from '../../Components/UploadImage' import { @@ -304,10 +304,7 @@ export const GroupPage: React.FC = ({ route }) => { /> )} - - {message?.created_at && - formatDistance(fromUnixTime(message.created_at), new Date())} - + {formatDate(message?.created_at)} {message ? ( @@ -369,7 +366,7 @@ export const GroupPage: React.FC = ({ route }) => { styles.card, // FIXME: can't find this color { - backgroundColor: '#001C37', + backgroundColor: theme.colors.elevation.level2, }, ]} > @@ -429,7 +426,7 @@ export const GroupPage: React.FC = ({ route }) => { <> )} {reply ? ( - + = ({ route }) => { setInput((prev) => `${prev} ${imageUri}`) setStartUpload(false) }} + onError={() => setStartUpload(false)} uploadingFile={uploadingFile} setUploadingFile={setUploadingFile} /> @@ -542,6 +540,7 @@ const styles = StyleSheet.create({ scaleY: -1, paddingLeft: 16, paddingRight: 16, + paddingBottom: 3, }, cardContentDate: { flexDirection: 'row', diff --git a/frontend/Pages/SendPage/index.tsx b/frontend/Pages/SendPage/index.tsx index a4badc6..f6193b2 100644 --- a/frontend/Pages/SendPage/index.tsx +++ b/frontend/Pages/SendPage/index.tsx @@ -219,6 +219,7 @@ export const SendPage: React.FC = ({ route }) => { setContent((prev) => `${prev}\n\n${imageUri}`) setStartUpload(false) }} + onError={() => setStartUpload(false)} uploadingFile={uploadingFile} setUploadingFile={setUploadingFile} /> diff --git a/frontend/lib/nostr/RelayPool/intex.ts b/frontend/lib/nostr/RelayPool/intex.ts index df1071c..43fe2e4 100644 --- a/frontend/lib/nostr/RelayPool/intex.ts +++ b/frontend/lib/nostr/RelayPool/intex.ts @@ -34,17 +34,49 @@ export interface ResilientAssignation { export const fallbackRelays = [ 'wss://brb.io', - 'wss://damus.io', 'wss://nostr-pub.wellorder.net', - 'wss://nostr.swiss-enigma.ch', - 'wss://nostr.onsats.org', - 'wss://nostr-pub.semisol.dev', - 'wss://nostr.openchain.fr', - 'wss://relay.nostr.info', - 'wss://nostr.oxtr.dev', - 'wss://nostr.ono.re', - 'wss://relay.grunch.dev', 'wss://nostr.developer.li', + 'wss://nostr.oxtr.dev', + 'wss://nostr.swiss-enigma.ch', + 'wss://relay.nostr.snblago.com', + 'wss://nos.lol', + 'wss://relay.austrich.net', + 'wss://nostr.cro.social', + 'wss://relay.koreus.social', + 'wss://spore.ws', + 'wss://nostr.web3infra.xyz', + 'wss://nostr.snblago.com', + 'wss://relay.nostrified.org', + 'wss://relay.ryzizub.com', + 'wss://relay.wellorder.net', + 'wss://nostr.btcmp.com', + 'wss://relay.nostromo.social', + 'wss://relay.stoner.com', + 'wss://nostr.massmux.com', + 'wss://nostr.robotesc.ro', + 'wss://relay.humanumest.social', + 'wss://relay-local.cowdle.gg', + 'wss://nostr-2.afarazit.eu', + 'wss://nostr.data.haus', + 'wss://nostr-pub.wellorder.net', + 'wss://nostr.thank.eu', + 'wss://relay-dev.cowdle.gg', + 'wss://nostrsxz4lbwe-nostr.functions.fnc.fr-par.scw.cloud', + 'wss://relay.nostrcheck.me', + 'wss://relay.nostrich.de', + 'wss://nostr.com.de', + 'wss://relay.nostr.scot', + 'wss://nostr.8e23.net', + 'wss://nostr.mouton.dev', + 'wss://nostr.l00p.org', + 'wss://nostr.island.network', + 'wss://nostr.handyjunky.com', + 'wss://relay.valera.co', + 'wss://relay.nostr.vet', + 'wss://tmp-relay.cesc.trade', + 'wss://relay.dwadziesciajeden.pl', + 'wss://nostr-1.afarazit.eu', + 'wss://lbrygen.xyz', ] class RelayPool { From ac285fdac882f73cfc42eea9d8de4ec934902f6f Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Mon, 20 Feb 2023 18:39:41 +0100 Subject: [PATCH 2/3] NProfiles --- frontend/Components/NoteCard/index.tsx | 14 ++----------- frontend/Components/ProfileShare/index.tsx | 21 +++++++++++++++---- frontend/Pages/HomePage/index.tsx | 6 ++++-- frontend/Pages/ProfilePage/index.tsx | 3 ++- frontend/lib/nostr/Nip19/index.ts | 24 +++++++++++++++++++++- 5 files changed, 48 insertions(+), 20 deletions(-) diff --git a/frontend/Components/NoteCard/index.tsx b/frontend/Components/NoteCard/index.tsx index 2cf3643..d775a1c 100644 --- a/frontend/Components/NoteCard/index.tsx +++ b/frontend/Components/NoteCard/index.tsx @@ -349,15 +349,7 @@ export const NoteCard: React.FC = ({ const generateZapInvoice: () => void = () => { const lud = note?.ln_address && note?.ln_address !== '' ? note?.ln_address : note?.lnurl - if ( - lud && - lud !== '' && - longPressZap && - database && - privateKey && - publicKey && - note?.pubkey - ) { + if (lud && lud !== '' && longPressZap && database && privateKey && publicKey && note?.pubkey) { setLoadingZap(true) lightningInvoice( database, @@ -473,9 +465,7 @@ export const NoteCard: React.FC = ({ /> )} onPress={() => setOpenLn(true)} - onLongPress={ - longPressZap ? generateZapInvoice : undefined - } + onLongPress={longPressZap ? generateZapInvoice : undefined} loading={loadingZap} > {note.zap_pubkey?.length > 0 ? formatBigNumber(zapsAmount) : ''} diff --git a/frontend/Components/ProfileShare/index.tsx b/frontend/Components/ProfileShare/index.tsx index 4fd5356..edc2957 100644 --- a/frontend/Components/ProfileShare/index.tsx +++ b/frontend/Components/ProfileShare/index.tsx @@ -6,18 +6,31 @@ import { IconButton, Snackbar, Text, TouchableRipple } from 'react-native-paper' 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 { getNprofile } from '../../lib/nostr/Nip19' import QRCode from 'react-native-qrcode-svg' +import { useContext } from 'react' +import { AppContext } from '../../Contexts/AppContext' +import { getUserRelays } from '../../Functions/DatabaseFunctions/NotesRelays' interface ProfileShareProps { user: User } export const ProfileShare: React.FC = ({ user }) => { + const { database } = useContext(AppContext) const bottomSheetShareRef = React.useRef(null) const [qrCode, setQrCode] = React.useState() const [showNotification, setShowNotification] = React.useState() - const nPub = React.useMemo(() => getNpub(user.id), [user]) + const [nProfile, setNProfile] = React.useState() + + React.useEffect(() => { + if (database && user.id) { + getUserRelays(database, user.id).then((results) => { + const urls = results.map((relay) => relay.relay_url) + setNProfile(getNprofile(user.id, urls)) + }) + } + }, [user]) return ( @@ -36,7 +49,7 @@ export const ProfileShare: React.FC = ({ user }) => { > = ({ user }) => { size={28} onPress={() => { setShowNotification('npubCopied') - Clipboard.setString(nPub ?? '') + Clipboard.setString(nProfile ?? '') bottomSheetShareRef.current?.close() }} /> diff --git a/frontend/Pages/HomePage/index.tsx b/frontend/Pages/HomePage/index.tsx index ff0f0c9..e0ca8be 100644 --- a/frontend/Pages/HomePage/index.tsx +++ b/frontend/Pages/HomePage/index.tsx @@ -88,11 +88,13 @@ export const HomePage: React.FC = () => { const goToEvent: () => void = () => { if (clipboardNip21) { const key = decode(clipboardNip21.replace('nostr:', '')) - if (key) { + if (key?.data) { if (key.type === 'nevent') { navigate('Note', { noteId: key.data }) - } else if (key.type === 'nprofile' || key.type === 'npub') { + } else if (key.type === 'npub') { navigate('Profile', { pubKey: key.data }) + } else if (key.type === 'nprofile' && key.data.pubkey) { + navigate('Profile', { pubKey: key.data.pubkey }) } } } diff --git a/frontend/Pages/ProfilePage/index.tsx b/frontend/Pages/ProfilePage/index.tsx index 66aac1a..13a6e13 100644 --- a/frontend/Pages/ProfilePage/index.tsx +++ b/frontend/Pages/ProfilePage/index.tsx @@ -22,6 +22,7 @@ import { handleInfinityScroll } from '../../Functions/NativeFunctions' import { useFocusEffect } from '@react-navigation/native' import ProfileData from '../../Components/ProfileData' import ProfileActions from '../../Components/ProfileActions' +import TextContent from '../../Components/TextContent' interface ProfilePageProps { route: { params: { pubKey: string } } @@ -170,7 +171,7 @@ export const ProfilePage: React.FC = ({ route }) => { - {user?.about} + diff --git a/frontend/lib/nostr/Nip19/index.ts b/frontend/lib/nostr/Nip19/index.ts index b3d4f46..49a8dd7 100644 --- a/frontend/lib/nostr/Nip19/index.ts +++ b/frontend/lib/nostr/Nip19/index.ts @@ -1,4 +1,11 @@ -import { decode, EventPointer, neventEncode, npubEncode, ProfilePointer } from 'nostr-tools/nip19' +import { + decode, + EventPointer, + neventEncode, + nprofileEncode, + npubEncode, + ProfilePointer, +} from 'nostr-tools/nip19' export function getNpub(key: string | undefined): string { if (!key) return '' @@ -13,6 +20,21 @@ export function getNpub(key: string | undefined): string { return key } +export function getNprofile(key: string, relays: string[]): string { + if (!key) return '' + + try { + return nprofileEncode({ + pubkey: key, + relays, + }) + } catch { + console.log('Error encoding') + } + + return key +} + export function getNevent(key: string | undefined): string { if (!key) return '' if (isPublicKey(key)) return key From 16ce1d47bb1091904f0216a9f82dd6e3e0f37b96 Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Mon, 20 Feb 2023 18:45:31 +0100 Subject: [PATCH 3/3] Fix --- frontend/Pages/ConversationPage/index.tsx | 3 +-- frontend/Pages/GroupPage/index.tsx | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/Pages/ConversationPage/index.tsx b/frontend/Pages/ConversationPage/index.tsx index 6ee5f0e..a7b2ad4 100644 --- a/frontend/Pages/ConversationPage/index.tsx +++ b/frontend/Pages/ConversationPage/index.tsx @@ -317,9 +317,8 @@ export const ConversationPage: React.FC = ({ route }) => diff --git a/frontend/Pages/GroupPage/index.tsx b/frontend/Pages/GroupPage/index.tsx index 6035a6a..47e1085 100644 --- a/frontend/Pages/GroupPage/index.tsx +++ b/frontend/Pages/GroupPage/index.tsx @@ -364,7 +364,6 @@ export const GroupPage: React.FC = ({ route }) => {