mirror of
https://github.com/KoalaSat/nostros.git
synced 2024-09-29 06:30:47 +00:00
Long pres zap (#355)
This commit is contained in:
commit
5b6d255ee0
@ -16,11 +16,8 @@ import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityI
|
|||||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||||
import { Kind } from 'nostr-tools'
|
import { Kind } from 'nostr-tools'
|
||||||
import { getUnixTime } from 'date-fns'
|
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 { UserContext } from '../../Contexts/UserContext'
|
||||||
import { requestInvoiceWithServiceParams, requestPayServiceParams } from 'lnurl-pay'
|
import { lightningInvoice } from '../../Functions/ServicesFunctions/ZapInvoice'
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
interface LnPaymentProps {
|
interface LnPaymentProps {
|
||||||
open: boolean
|
open: boolean
|
||||||
@ -86,57 +83,26 @@ export const LnPayment: React.FC<LnPaymentProps> = ({ open, setOpen, note, user
|
|||||||
setIsZap(zap)
|
setIsZap(zap)
|
||||||
const lud = lnAddress && lnAddress !== '' ? lnAddress : lnurl
|
const lud = lnAddress && lnAddress !== '' ? lnAddress : lnurl
|
||||||
|
|
||||||
if (lud && lud !== '' && monto !== '') {
|
if (lud && lud !== '' && monto !== '' && database && privateKey && publicKey && userId) {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
|
||||||
const tokens: number = parseInt(monto, 10) ?? 0
|
lightningInvoice(
|
||||||
let nostr: string
|
database,
|
||||||
|
lud,
|
||||||
if (zap && database && privateKey && publicKey && zapPubkey && userId) {
|
parseInt(monto, 10),
|
||||||
const relays: Relay[] = await getRelays(database)
|
privateKey,
|
||||||
const tags = [
|
publicKey,
|
||||||
['p', userId],
|
userId,
|
||||||
['amount', (tokens * 1000).toString()],
|
zap,
|
||||||
['relays', ...relays.map((relay) => relay.url)],
|
zapPubkey,
|
||||||
]
|
|
||||||
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,
|
|
||||||
comment,
|
comment,
|
||||||
fetchGet: async ({ url, params }) => {
|
note?.id,
|
||||||
if (params && nostr && serviceParams.rawData.allowsNostr) {
|
)
|
||||||
params.nostr = nostr
|
.then((invoice) => {
|
||||||
}
|
if (invoice) setInvoice(invoice)
|
||||||
const response = await axios.get(url, {
|
|
||||||
params,
|
|
||||||
})
|
|
||||||
console.log(response)
|
|
||||||
return response.data
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((action) => {
|
|
||||||
if (action.hasValidAmount && action.invoice) {
|
|
||||||
setInvoice(action.invoice)
|
|
||||||
}
|
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch(() => {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,9 @@ import { SvgXml } from 'react-native-svg'
|
|||||||
import { reactionIcon } from '../../Constants/Theme'
|
import { reactionIcon } from '../../Constants/Theme'
|
||||||
import LnPayment from '../LnPayment'
|
import LnPayment from '../LnPayment'
|
||||||
import { getZapsAmount } from '../../Functions/DatabaseFunctions/Zaps'
|
import { getZapsAmount } from '../../Functions/DatabaseFunctions/Zaps'
|
||||||
|
import { lightningInvoice } from '../../Functions/ServicesFunctions/ZapInvoice'
|
||||||
|
import LnPreview from '../LnPreview'
|
||||||
|
import { getNpub } from '../../lib/nostr/Nip19'
|
||||||
|
|
||||||
interface NoteCardProps {
|
interface NoteCardProps {
|
||||||
note?: Note
|
note?: Note
|
||||||
@ -72,7 +75,8 @@ export const NoteCard: React.FC<NoteCardProps> = ({
|
|||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const { publicKey, privateKey } = React.useContext(UserContext)
|
const { publicKey, privateKey } = React.useContext(UserContext)
|
||||||
const { relayPool, lastEventId, setDisplayrelayDrawer } = useContext(RelayPoolContext)
|
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<boolean>(false)
|
const [relayAdded, setRelayAdded] = useState<boolean>(false)
|
||||||
const [positiveReactions, setPositiveReactions] = useState<number>(0)
|
const [positiveReactions, setPositiveReactions] = useState<number>(0)
|
||||||
const [negativeReactions, setNegativeReactions] = useState<number>(0)
|
const [negativeReactions, setNegativeReactions] = useState<number>(0)
|
||||||
@ -87,6 +91,8 @@ export const NoteCard: React.FC<NoteCardProps> = ({
|
|||||||
const [repost, setRepost] = useState<Note>()
|
const [repost, setRepost] = useState<Note>()
|
||||||
const [openLn, setOpenLn] = React.useState<boolean>(false)
|
const [openLn, setOpenLn] = React.useState<boolean>(false)
|
||||||
const [showReactions, setShowReactions] = React.useState<boolean>(false)
|
const [showReactions, setShowReactions] = React.useState<boolean>(false)
|
||||||
|
const [loadingZap, setLoadingZap] = React.useState<boolean>(false)
|
||||||
|
const [zapInvoice, setZapInvoice] = React.useState<string>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (database && publicKey && note?.id) {
|
if (database && publicKey && note?.id) {
|
||||||
@ -340,6 +346,33 @@ export const NoteCard: React.FC<NoteCardProps> = ({
|
|||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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 = () => {
|
const reactionsCount: () => number = () => {
|
||||||
if (userDownvoted) return negativeReactions
|
if (userDownvoted) return negativeReactions
|
||||||
if (userUpvoted) return positiveReactions
|
if (userUpvoted) return positiveReactions
|
||||||
@ -432,10 +465,13 @@ export const NoteCard: React.FC<NoteCardProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
onPress={() => setOpenLn(true)}
|
onPress={() => setOpenLn(true)}
|
||||||
|
onLongPress={longPressZap ? generateZapInvoice : undefined}
|
||||||
|
loading={loadingZap}
|
||||||
>
|
>
|
||||||
{note.zap_pubkey?.length > 0 ? formatBigNumber(zapsAmount) : ''}
|
{note.zap_pubkey?.length > 0 ? formatBigNumber(zapsAmount) : ''}
|
||||||
</Button>
|
</Button>
|
||||||
{openLn && <LnPayment open={openLn} setOpen={setOpenLn} note={note} />}
|
{openLn && <LnPayment open={openLn} setOpen={setOpenLn} note={note} />}
|
||||||
|
{zapInvoice && <LnPreview invoice={zapInvoice} setInvoice={setZapInvoice} />}
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
)}
|
)}
|
||||||
<Card.Content style={styles.relayList}>
|
<Card.Content style={styles.relayList}>
|
||||||
|
@ -4,8 +4,8 @@ import { Text, useTheme } from 'react-native-paper'
|
|||||||
import { getNip05Domain, usernamePubKey } from '../../Functions/RelayFunctions/Users'
|
import { getNip05Domain, usernamePubKey } from '../../Functions/RelayFunctions/Users'
|
||||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
|
||||||
import NostrosAvatar from '../NostrosAvatar'
|
import NostrosAvatar from '../NostrosAvatar'
|
||||||
import { fromUnixTime, formatDistance } from 'date-fns'
|
|
||||||
import { getNpub } from '../../lib/nostr/Nip19'
|
import { getNpub } from '../../lib/nostr/Nip19'
|
||||||
|
import { formatDate } from '../../Functions/NativeFunctions'
|
||||||
|
|
||||||
interface ProfileCardProps {
|
interface ProfileCardProps {
|
||||||
username?: string
|
username?: string
|
||||||
@ -32,10 +32,7 @@ export const ProfileData: React.FC<ProfileCardProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const nPub = React.useMemo(() => (publicKey ? getNpub(publicKey) : ''), [publicKey])
|
const nPub = React.useMemo(() => (publicKey ? getNpub(publicKey) : ''), [publicKey])
|
||||||
const date = React.useMemo(
|
const date = React.useMemo(() => formatDate(timestamp), [timestamp])
|
||||||
() => (timestamp ? formatDistance(fromUnixTime(timestamp), new Date()) : null),
|
|
||||||
[timestamp],
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
|
@ -6,18 +6,31 @@ import { IconButton, Snackbar, Text, TouchableRipple } from 'react-native-paper'
|
|||||||
import { User } from '../../Functions/DatabaseFunctions/Users'
|
import { User } from '../../Functions/DatabaseFunctions/Users'
|
||||||
import Share from 'react-native-share'
|
import Share from 'react-native-share'
|
||||||
import RBSheet from 'react-native-raw-bottom-sheet'
|
import RBSheet from 'react-native-raw-bottom-sheet'
|
||||||
import { getNpub } from '../../lib/nostr/Nip19'
|
import { getNprofile } from '../../lib/nostr/Nip19'
|
||||||
import QRCode from 'react-native-qrcode-svg'
|
import QRCode from 'react-native-qrcode-svg'
|
||||||
|
import { useContext } from 'react'
|
||||||
|
import { AppContext } from '../../Contexts/AppContext'
|
||||||
|
import { getUserRelays } from '../../Functions/DatabaseFunctions/NotesRelays'
|
||||||
|
|
||||||
interface ProfileShareProps {
|
interface ProfileShareProps {
|
||||||
user: User
|
user: User
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProfileShare: React.FC<ProfileShareProps> = ({ user }) => {
|
export const ProfileShare: React.FC<ProfileShareProps> = ({ user }) => {
|
||||||
|
const { database } = useContext(AppContext)
|
||||||
const bottomSheetShareRef = React.useRef<RBSheet>(null)
|
const bottomSheetShareRef = React.useRef<RBSheet>(null)
|
||||||
const [qrCode, setQrCode] = React.useState<any>()
|
const [qrCode, setQrCode] = React.useState<any>()
|
||||||
const [showNotification, setShowNotification] = React.useState<undefined | string>()
|
const [showNotification, setShowNotification] = React.useState<undefined | string>()
|
||||||
const nPub = React.useMemo(() => getNpub(user.id), [user])
|
const [nProfile, setNProfile] = React.useState<string>()
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<View style={styles.mainLayout}>
|
<View style={styles.mainLayout}>
|
||||||
@ -36,7 +49,7 @@ export const ProfileShare: React.FC<ProfileShareProps> = ({ user }) => {
|
|||||||
>
|
>
|
||||||
<QRCode
|
<QRCode
|
||||||
quietZone={8}
|
quietZone={8}
|
||||||
value={`nostr:${nPub}`}
|
value={`nostr:${nProfile}`}
|
||||||
size={Dimensions.get('window').width - 64}
|
size={Dimensions.get('window').width - 64}
|
||||||
logoBorderRadius={50}
|
logoBorderRadius={50}
|
||||||
logoSize={100}
|
logoSize={100}
|
||||||
@ -51,7 +64,7 @@ export const ProfileShare: React.FC<ProfileShareProps> = ({ user }) => {
|
|||||||
size={28}
|
size={28}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setShowNotification('npubCopied')
|
setShowNotification('npubCopied')
|
||||||
Clipboard.setString(nPub ?? '')
|
Clipboard.setString(nProfile ?? '')
|
||||||
bottomSheetShareRef.current?.close()
|
bottomSheetShareRef.current?.close()
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -13,6 +13,7 @@ interface UploadImageProps {
|
|||||||
setImageUri: (uri: string) => void
|
setImageUri: (uri: string) => void
|
||||||
uploadingFile: boolean
|
uploadingFile: boolean
|
||||||
setUploadingFile: (uploading: boolean) => void
|
setUploadingFile: (uploading: boolean) => void
|
||||||
|
onError: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UploadImage: React.FC<UploadImageProps> = ({
|
export const UploadImage: React.FC<UploadImageProps> = ({
|
||||||
@ -21,6 +22,7 @@ export const UploadImage: React.FC<UploadImageProps> = ({
|
|||||||
setImageUri,
|
setImageUri,
|
||||||
uploadingFile,
|
uploadingFile,
|
||||||
setUploadingFile,
|
setUploadingFile,
|
||||||
|
onError,
|
||||||
}) => {
|
}) => {
|
||||||
const { getImageHostingService } = useContext(AppContext)
|
const { getImageHostingService } = useContext(AppContext)
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
@ -46,10 +48,12 @@ export const UploadImage: React.FC<UploadImageProps> = ({
|
|||||||
setUploadingFile(false)
|
setUploadingFile(false)
|
||||||
bottomSheetImageRef.current?.open()
|
bottomSheetImageRef.current?.open()
|
||||||
} else {
|
} else {
|
||||||
|
onError()
|
||||||
setUploadingFile(false)
|
setUploadingFile(false)
|
||||||
setShowNotification('imageUploadErro')
|
setShowNotification('imageUploadErro')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
onError()
|
||||||
setUploadingFile(false)
|
setUploadingFile(false)
|
||||||
setShowNotification('imageUploadErro')
|
setShowNotification('imageUploadErro')
|
||||||
}
|
}
|
||||||
@ -72,6 +76,7 @@ export const UploadImage: React.FC<UploadImageProps> = ({
|
|||||||
bottomSheetImageRef.current?.close()
|
bottomSheetImageRef.current?.close()
|
||||||
setUploadingFile(false)
|
setUploadingFile(false)
|
||||||
setShowNotification('imageUploadErro')
|
setShowNotification('imageUploadErro')
|
||||||
|
onError()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,6 +127,7 @@ export const UploadImage: React.FC<UploadImageProps> = ({
|
|||||||
onPress={() => {
|
onPress={() => {
|
||||||
bottomSheetImageRef.current?.close()
|
bottomSheetImageRef.current?.close()
|
||||||
setImageUpload(undefined)
|
setImageUpload(undefined)
|
||||||
|
onError()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('uploadImage.cancel')}
|
{t('uploadImage.cancel')}
|
||||||
|
@ -37,6 +37,8 @@ export interface AppContextProps {
|
|||||||
setDisplayUserDrawer: (displayUserDrawer: string | undefined) => void
|
setDisplayUserDrawer: (displayUserDrawer: string | undefined) => void
|
||||||
refreshBottomBarAt?: number
|
refreshBottomBarAt?: number
|
||||||
setRefreshBottomBarAt: (refreshBottomBarAt: number) => void
|
setRefreshBottomBarAt: (refreshBottomBarAt: number) => void
|
||||||
|
longPressZap?: number | undefined
|
||||||
|
setLongPressZap: (longPressZap: number | undefined) => void
|
||||||
pushedTab?: string
|
pushedTab?: string
|
||||||
setPushedTab: (pushedTab: string) => void
|
setPushedTab: (pushedTab: string) => void
|
||||||
}
|
}
|
||||||
@ -76,6 +78,8 @@ export const initialAppContext: AppContextProps = {
|
|||||||
getSatoshiSymbol: () => <></>,
|
getSatoshiSymbol: () => <></>,
|
||||||
setClipboardNip21: () => {},
|
setClipboardNip21: () => {},
|
||||||
setDisplayUserDrawer: () => {},
|
setDisplayUserDrawer: () => {},
|
||||||
|
longPressZap: undefined,
|
||||||
|
setLongPressZap: () => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.Element => {
|
export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.Element => {
|
||||||
@ -92,6 +96,7 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E
|
|||||||
const [imageHostingService, setImageHostingService] = React.useState<string>(
|
const [imageHostingService, setImageHostingService] = React.useState<string>(
|
||||||
initialAppContext.imageHostingService,
|
initialAppContext.imageHostingService,
|
||||||
)
|
)
|
||||||
|
const [longPressZap, setLongPressZap] = React.useState<number>()
|
||||||
const [notificationSeenAt, setNotificationSeenAt] = React.useState<number>(0)
|
const [notificationSeenAt, setNotificationSeenAt] = React.useState<number>(0)
|
||||||
const [refreshBottomBarAt, setRefreshBottomBarAt] = React.useState<number>(0)
|
const [refreshBottomBarAt, setRefreshBottomBarAt] = React.useState<number>(0)
|
||||||
const [satoshi, setSatoshi] = React.useState<'kebab' | 'sats'>(initialAppContext.satoshi)
|
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,
|
config.image_hosting_service ?? initialAppContext.imageHostingService,
|
||||||
)
|
)
|
||||||
setLanguage(config.language ?? initialAppContext.language)
|
setLanguage(config.language ?? initialAppContext.language)
|
||||||
|
setLongPressZap(config.long_press_zap ?? initialAppContext.longPressZap)
|
||||||
} else {
|
} else {
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
show_public_images: initialAppContext.showPublicImages,
|
show_public_images: initialAppContext.showPublicImages,
|
||||||
@ -158,6 +164,7 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E
|
|||||||
last_pets_at: 0,
|
last_pets_at: 0,
|
||||||
language: initialAppContext.language,
|
language: initialAppContext.language,
|
||||||
relay_coloruring: initialAppContext.relayColouring,
|
relay_coloruring: initialAppContext.relayColouring,
|
||||||
|
long_press_zap: initialAppContext.longPressZap,
|
||||||
}
|
}
|
||||||
SInfo.setItem('config', JSON.stringify(config), {})
|
SInfo.setItem('config', JSON.stringify(config), {})
|
||||||
}
|
}
|
||||||
@ -229,6 +236,8 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E
|
|||||||
setRefreshBottomBarAt,
|
setRefreshBottomBarAt,
|
||||||
pushedTab,
|
pushedTab,
|
||||||
setPushedTab,
|
setPushedTab,
|
||||||
|
longPressZap,
|
||||||
|
setLongPressZap,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@ -177,7 +177,7 @@ export const getUserGroupMessagesCount: (
|
|||||||
SELECT
|
SELECT
|
||||||
COUNT(*)
|
COUNT(*)
|
||||||
FROM nostros_group_messages
|
FROM nostros_group_messages
|
||||||
WHERE (user_mentioned != NULL AND user_mentioned = 1)
|
WHERE user_mentioned = 1
|
||||||
AND (read = NULL OR read = 0)
|
AND (read = NULL OR read = 0)
|
||||||
`
|
`
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { endOfYesterday, format, formatDistanceToNow, fromUnixTime, isBefore } from 'date-fns'
|
||||||
|
|
||||||
export const handleInfinityScroll: (event: any) => boolean = (event) => {
|
export const handleInfinityScroll: (event: any) => boolean = (event) => {
|
||||||
const mHeight = event.nativeEvent.layoutMeasurement.height
|
const mHeight = event.nativeEvent.layoutMeasurement.height
|
||||||
const cSize = event.nativeEvent.contentSize.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) => {
|
export const formatBigNumber: (num: number | undefined) => string = (num) => {
|
||||||
if (num === undefined) return ''
|
if (num === undefined) return ''
|
||||||
|
|
||||||
|
83
frontend/Functions/ServicesFunctions/ZapInvoice/index.ts
Normal file
83
frontend/Functions/ServicesFunctions/ZapInvoice/index.ts
Normal file
@ -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<string | null> = 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<string>((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())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
@ -82,7 +82,16 @@
|
|||||||
"imageHostingService": "Bilder Hosting Service",
|
"imageHostingService": "Bilder Hosting Service",
|
||||||
"random": "zufällig",
|
"random": "zufällig",
|
||||||
"language": "Sprache",
|
"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": {
|
"noteCard": {
|
||||||
"answering": "{{pubkey}} antworten",
|
"answering": "{{pubkey}} antworten",
|
||||||
|
@ -82,7 +82,16 @@
|
|||||||
"imageHostingService": "Image hosting service",
|
"imageHostingService": "Image hosting service",
|
||||||
"random": "Random",
|
"random": "Random",
|
||||||
"language": "Language",
|
"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": {
|
"noteCard": {
|
||||||
"answering": "Answer to {{pubkey}}",
|
"answering": "Answer to {{pubkey}}",
|
||||||
|
@ -103,7 +103,16 @@
|
|||||||
"imageHostingService": "Servicio de subida de imágenes",
|
"imageHostingService": "Servicio de subida de imágenes",
|
||||||
"random": "Aleatorio",
|
"random": "Aleatorio",
|
||||||
"language": "Idioma",
|
"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": {
|
"noteCard": {
|
||||||
"answering": "Responder a {{pubkey}}",
|
"answering": "Responder a {{pubkey}}",
|
||||||
|
@ -110,7 +110,16 @@
|
|||||||
"satoshi": "Symbole de satoshi",
|
"satoshi": "Symbole de satoshi",
|
||||||
"imageHostingService": "Service d'hébergement d'images",
|
"imageHostingService": "Service d'hébergement d'images",
|
||||||
"language": "Langue",
|
"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": {
|
"noteCard": {
|
||||||
"answering": "Répondre à {{pubkey}}",
|
"answering": "Répondre à {{pubkey}}",
|
||||||
|
@ -103,7 +103,16 @@
|
|||||||
"satoshi": "Satoshi symbol",
|
"satoshi": "Satoshi symbol",
|
||||||
"random": "Random",
|
"random": "Random",
|
||||||
"language": "Язык",
|
"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": {
|
"noteCard": {
|
||||||
"answering": "Ответить {{pubkey}}",
|
"answering": "Ответить {{pubkey}}",
|
||||||
|
@ -102,7 +102,16 @@
|
|||||||
"imageHostingService": "图片托管服务",
|
"imageHostingService": "图片托管服务",
|
||||||
"random": "随机",
|
"random": "随机",
|
||||||
"language": "语言",
|
"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": {
|
"noteCard": {
|
||||||
"answering": "回复 {{pubkey}}",
|
"answering": "回复 {{pubkey}}",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { FlatList, StyleSheet, Text } from 'react-native'
|
import { FlatList, StyleSheet } from 'react-native'
|
||||||
import { Divider, List, Switch, useTheme } from 'react-native-paper'
|
import { Button, Divider, List, Switch, Text, TextInput, useTheme } from 'react-native-paper'
|
||||||
import SInfo from 'react-native-sensitive-info'
|
import SInfo from 'react-native-sensitive-info'
|
||||||
import RBSheet from 'react-native-raw-bottom-sheet'
|
import RBSheet from 'react-native-raw-bottom-sheet'
|
||||||
import { AppContext } from '../../Contexts/AppContext'
|
import { AppContext } from '../../Contexts/AppContext'
|
||||||
@ -16,6 +16,7 @@ export interface Config {
|
|||||||
image_hosting_service: string
|
image_hosting_service: string
|
||||||
language: string
|
language: string
|
||||||
relay_coloruring: boolean
|
relay_coloruring: boolean
|
||||||
|
long_press_zap: number | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ConfigPage: React.FC = () => {
|
export const ConfigPage: React.FC = () => {
|
||||||
@ -35,10 +36,14 @@ export const ConfigPage: React.FC = () => {
|
|||||||
setLanguage,
|
setLanguage,
|
||||||
relayColouring,
|
relayColouring,
|
||||||
setRelayColouring,
|
setRelayColouring,
|
||||||
|
longPressZap,
|
||||||
|
setLongPressZap,
|
||||||
} = React.useContext(AppContext)
|
} = React.useContext(AppContext)
|
||||||
const bottomSheetSatoshiRef = React.useRef<RBSheet>(null)
|
const bottomSheetSatoshiRef = React.useRef<RBSheet>(null)
|
||||||
const bottomSheetImageHostingRef = React.useRef<RBSheet>(null)
|
const bottomSheetImageHostingRef = React.useRef<RBSheet>(null)
|
||||||
const bottomSheetLanguageRef = React.useRef<RBSheet>(null)
|
const bottomSheetLanguageRef = React.useRef<RBSheet>(null)
|
||||||
|
const bottomSheetLongPressZapRef = React.useRef<RBSheet>(null)
|
||||||
|
const [zapAmount, setZapAmount] = React.useState<string | undefined>(longPressZap?.toString())
|
||||||
|
|
||||||
React.useEffect(() => {}, [showPublicImages, showSensitive, satoshi])
|
React.useEffect(() => {}, [showPublicImages, showSensitive, satoshi])
|
||||||
|
|
||||||
@ -126,6 +131,25 @@ export const ConfigPage: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<List.Item title={t('configPage.app')} />
|
||||||
|
<Divider />
|
||||||
|
<List.Item
|
||||||
|
title={t('configPage.language')}
|
||||||
|
onPress={() => bottomSheetLanguageRef.current?.open()}
|
||||||
|
right={() => <Text>{t(`language.${language}`)}</Text>}
|
||||||
|
/>
|
||||||
|
<List.Item
|
||||||
|
title={t('configPage.imageHostingService')}
|
||||||
|
onPress={() => bottomSheetImageHostingRef.current?.open()}
|
||||||
|
right={() => (
|
||||||
|
<Text style={{ color: theme.colors.onSurfaceVariant }}>
|
||||||
|
{imageHostingServices[imageHostingService]?.uri ??
|
||||||
|
t(`configPage.${imageHostingService}`)}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<List.Item title={t('configPage.feed')} />
|
||||||
|
<Divider />
|
||||||
<List.Item
|
<List.Item
|
||||||
title={t('configPage.showPublicImages')}
|
title={t('configPage.showPublicImages')}
|
||||||
right={() => (
|
right={() => (
|
||||||
@ -174,23 +198,19 @@ export const ConfigPage: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<List.Item
|
<List.Item title={t('configPage.zaps')} />
|
||||||
title={t('configPage.language')}
|
<Divider />
|
||||||
onPress={() => bottomSheetLanguageRef.current?.open()}
|
|
||||||
right={() => <Text>{t(`language.${language}`)}</Text>}
|
|
||||||
/>
|
|
||||||
<List.Item
|
<List.Item
|
||||||
title={t('configPage.satoshi')}
|
title={t('configPage.satoshi')}
|
||||||
onPress={() => bottomSheetSatoshiRef.current?.open()}
|
onPress={() => bottomSheetSatoshiRef.current?.open()}
|
||||||
right={() => getSatoshiSymbol(25)}
|
right={() => getSatoshiSymbol(25)}
|
||||||
/>
|
/>
|
||||||
<List.Item
|
<List.Item
|
||||||
title={t('configPage.imageHostingService')}
|
title={t('configPage.longPressZap')}
|
||||||
onPress={() => bottomSheetImageHostingRef.current?.open()}
|
onPress={() => bottomSheetLongPressZapRef.current?.open()}
|
||||||
right={() => (
|
right={() => (
|
||||||
<Text style={{ color: theme.colors.onSurfaceVariant }}>
|
<Text style={{ color: theme.colors.onSurfaceVariant }}>
|
||||||
{imageHostingServices[imageHostingService]?.uri ??
|
{longPressZap ?? t('configPage.disabled')}
|
||||||
t(`configPage.${imageHostingService}`)}
|
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -225,6 +245,56 @@ export const ConfigPage: React.FC = () => {
|
|||||||
ItemSeparatorComponent={Divider}
|
ItemSeparatorComponent={Divider}
|
||||||
/>
|
/>
|
||||||
</RBSheet>
|
</RBSheet>
|
||||||
|
<RBSheet
|
||||||
|
ref={bottomSheetLongPressZapRef}
|
||||||
|
closeOnDragDown={true}
|
||||||
|
customStyles={bottomSheetStyles}
|
||||||
|
>
|
||||||
|
<Text variant='titleLarge'>{t('configPage.longPressZap')}</Text>
|
||||||
|
<Text style={styles.input} variant='bodyMedium'>
|
||||||
|
{t('configPage.longPressZapDescription')}
|
||||||
|
</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
mode='outlined'
|
||||||
|
label={t('configPage.defaultZapAmount') ?? ''}
|
||||||
|
onChangeText={setZapAmount}
|
||||||
|
value={zapAmount}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
mode='contained'
|
||||||
|
style={styles.input}
|
||||||
|
onPress={() => {
|
||||||
|
if (zapAmount) {
|
||||||
|
SInfo.getItem('config', {}).then((result) => {
|
||||||
|
const config: Config = JSON.parse(result)
|
||||||
|
config.long_press_zap = parseInt(zapAmount, 10)
|
||||||
|
SInfo.setItem('config', JSON.stringify(config), {})
|
||||||
|
setLongPressZap(parseInt(zapAmount, 10))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
bottomSheetLongPressZapRef.current?.close()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('configPage.update')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
mode='outlined'
|
||||||
|
style={styles.input}
|
||||||
|
onPress={() => {
|
||||||
|
SInfo.getItem('config', {}).then((result) => {
|
||||||
|
const config: Config = JSON.parse(result)
|
||||||
|
config.long_press_zap = undefined
|
||||||
|
SInfo.setItem('config', JSON.stringify(config), {})
|
||||||
|
setLongPressZap(undefined)
|
||||||
|
setZapAmount(undefined)
|
||||||
|
})
|
||||||
|
bottomSheetLongPressZapRef.current?.close()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('configPage.disable')}
|
||||||
|
</Button>
|
||||||
|
</RBSheet>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -234,6 +304,9 @@ const styles = StyleSheet.create({
|
|||||||
fontFamily: 'Satoshi-Symbol',
|
fontFamily: 'Satoshi-Symbol',
|
||||||
fontSize: 25,
|
fontSize: 25,
|
||||||
},
|
},
|
||||||
|
input: {
|
||||||
|
marginTop: 16,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export default ConfigPage
|
export default ConfigPage
|
||||||
|
@ -20,7 +20,7 @@ import {
|
|||||||
import { getUser, User } from '../../Functions/DatabaseFunctions/Users'
|
import { getUser, User } from '../../Functions/DatabaseFunctions/Users'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { username, usernamePubKey, usersToTags } from '../../Functions/RelayFunctions/Users'
|
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 TextContent from '../../Components/TextContent'
|
||||||
import { encrypt, decrypt } from '../../lib/nostr/Nip04'
|
import { encrypt, decrypt } from '../../lib/nostr/Nip04'
|
||||||
import {
|
import {
|
||||||
@ -36,7 +36,7 @@ import { UserContext } from '../../Contexts/UserContext'
|
|||||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
|
||||||
import { useFocusEffect } from '@react-navigation/native'
|
import { useFocusEffect } from '@react-navigation/native'
|
||||||
import { Kind } from 'nostr-tools'
|
import { Kind } from 'nostr-tools'
|
||||||
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
|
import { formatDate, handleInfinityScroll } from '../../Functions/NativeFunctions'
|
||||||
import NostrosAvatar from '../../Components/NostrosAvatar'
|
import NostrosAvatar from '../../Components/NostrosAvatar'
|
||||||
import UploadImage from '../../Components/UploadImage'
|
import UploadImage from '../../Components/UploadImage'
|
||||||
import { Swipeable } from 'react-native-gesture-handler'
|
import { Swipeable } from 'react-native-gesture-handler'
|
||||||
@ -246,9 +246,7 @@ export const ConversationPage: React.FC<ConversationPageProps> = ({ route }) =>
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
<Text>
|
<Text>{formatDate(message?.created_at)}</Text>
|
||||||
{message?.created_at && formatDistance(fromUnixTime(message.created_at), new Date())}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{message ? (
|
{message ? (
|
||||||
@ -319,9 +317,8 @@ export const ConversationPage: React.FC<ConversationPageProps> = ({ route }) =>
|
|||||||
<Card
|
<Card
|
||||||
style={[
|
style={[
|
||||||
styles.card,
|
styles.card,
|
||||||
// FIXME: can't find this color
|
|
||||||
{
|
{
|
||||||
backgroundColor: '#001C37',
|
backgroundColor: theme.colors.elevation.level2,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@ -456,6 +453,7 @@ export const ConversationPage: React.FC<ConversationPageProps> = ({ route }) =>
|
|||||||
setInput((prev) => `${prev} ${imageUri}`)
|
setInput((prev) => `${prev} ${imageUri}`)
|
||||||
setStartUpload(false)
|
setStartUpload(false)
|
||||||
}}
|
}}
|
||||||
|
onError={() => setStartUpload(false)}
|
||||||
uploadingFile={uploadingFile}
|
uploadingFile={uploadingFile}
|
||||||
setUploadingFile={setUploadingFile}
|
setUploadingFile={setUploadingFile}
|
||||||
/>
|
/>
|
||||||
|
@ -38,8 +38,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
|
||||||
import { useFocusEffect } from '@react-navigation/native'
|
import { useFocusEffect } from '@react-navigation/native'
|
||||||
import ProfileData from '../../Components/ProfileData'
|
import ProfileData from '../../Components/ProfileData'
|
||||||
import { fromUnixTime, formatDistance } from 'date-fns'
|
import { formatDate, handleInfinityScroll } from '../../Functions/NativeFunctions'
|
||||||
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
|
|
||||||
|
|
||||||
export const ConversationsFeed: React.FC = () => {
|
export const ConversationsFeed: React.FC = () => {
|
||||||
const initialPageSize = 14
|
const initialPageSize = 14
|
||||||
@ -148,7 +147,7 @@ export const ConversationsFeed: React.FC = () => {
|
|||||||
</View>
|
</View>
|
||||||
<View style={styles.contactInfo}>
|
<View style={styles.contactInfo}>
|
||||||
<View style={styles.contactDate}>
|
<View style={styles.contactDate}>
|
||||||
<Text>{formatDistance(fromUnixTime(item.created_at), new Date())}</Text>
|
<Text>{formatDate(item?.created_at)}</Text>
|
||||||
{item.pubkey !== publicKey && !item.read && <Badge size={16}></Badge>}
|
{item.pubkey !== publicKey && !item.read && <Badge size={16}></Badge>}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
@ -13,7 +13,7 @@ import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
|||||||
import { Event } from '../../lib/nostr/Events'
|
import { Event } from '../../lib/nostr/Events'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { formatPubKey, username, usernamePubKey } from '../../Functions/RelayFunctions/Users'
|
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 TextContent from '../../Components/TextContent'
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@ -28,7 +28,7 @@ import { UserContext } from '../../Contexts/UserContext'
|
|||||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
|
||||||
import { useFocusEffect } from '@react-navigation/native'
|
import { useFocusEffect } from '@react-navigation/native'
|
||||||
import { Kind } from 'nostr-tools'
|
import { Kind } from 'nostr-tools'
|
||||||
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
|
import { formatDate, handleInfinityScroll } from '../../Functions/NativeFunctions'
|
||||||
import NostrosAvatar from '../../Components/NostrosAvatar'
|
import NostrosAvatar from '../../Components/NostrosAvatar'
|
||||||
import UploadImage from '../../Components/UploadImage'
|
import UploadImage from '../../Components/UploadImage'
|
||||||
import {
|
import {
|
||||||
@ -304,10 +304,7 @@ export const GroupPage: React.FC<GroupPageProps> = ({ route }) => {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
<Text>
|
<Text>{formatDate(message?.created_at)}</Text>
|
||||||
{message?.created_at &&
|
|
||||||
formatDistance(fromUnixTime(message.created_at), new Date())}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{message ? (
|
{message ? (
|
||||||
@ -367,9 +364,8 @@ export const GroupPage: React.FC<GroupPageProps> = ({ route }) => {
|
|||||||
<Card
|
<Card
|
||||||
style={[
|
style={[
|
||||||
styles.card,
|
styles.card,
|
||||||
// FIXME: can't find this color
|
|
||||||
{
|
{
|
||||||
backgroundColor: '#001C37',
|
backgroundColor: theme.colors.elevation.level2,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@ -429,7 +425,7 @@ export const GroupPage: React.FC<GroupPageProps> = ({ route }) => {
|
|||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
{reply ? (
|
{reply ? (
|
||||||
<View style={[styles.reply, { backgroundColor: theme.colors.backdrop }]}>
|
<View style={[styles.reply, { backgroundColor: theme.colors.elevation.level2 }]}>
|
||||||
<MaterialCommunityIcons
|
<MaterialCommunityIcons
|
||||||
name='reply'
|
name='reply'
|
||||||
size={25}
|
size={25}
|
||||||
@ -502,6 +498,7 @@ export const GroupPage: React.FC<GroupPageProps> = ({ route }) => {
|
|||||||
setInput((prev) => `${prev} ${imageUri}`)
|
setInput((prev) => `${prev} ${imageUri}`)
|
||||||
setStartUpload(false)
|
setStartUpload(false)
|
||||||
}}
|
}}
|
||||||
|
onError={() => setStartUpload(false)}
|
||||||
uploadingFile={uploadingFile}
|
uploadingFile={uploadingFile}
|
||||||
setUploadingFile={setUploadingFile}
|
setUploadingFile={setUploadingFile}
|
||||||
/>
|
/>
|
||||||
@ -542,6 +539,7 @@ const styles = StyleSheet.create({
|
|||||||
scaleY: -1,
|
scaleY: -1,
|
||||||
paddingLeft: 16,
|
paddingLeft: 16,
|
||||||
paddingRight: 16,
|
paddingRight: 16,
|
||||||
|
paddingBottom: 3,
|
||||||
},
|
},
|
||||||
cardContentDate: {
|
cardContentDate: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
@ -88,11 +88,13 @@ export const HomePage: React.FC = () => {
|
|||||||
const goToEvent: () => void = () => {
|
const goToEvent: () => void = () => {
|
||||||
if (clipboardNip21) {
|
if (clipboardNip21) {
|
||||||
const key = decode(clipboardNip21.replace('nostr:', ''))
|
const key = decode(clipboardNip21.replace('nostr:', ''))
|
||||||
if (key) {
|
if (key?.data) {
|
||||||
if (key.type === 'nevent') {
|
if (key.type === 'nevent') {
|
||||||
navigate('Note', { noteId: key.data })
|
navigate('Note', { noteId: key.data })
|
||||||
} else if (key.type === 'nprofile' || key.type === 'npub') {
|
} else if (key.type === 'npub') {
|
||||||
navigate('Profile', { pubKey: key.data })
|
navigate('Profile', { pubKey: key.data })
|
||||||
|
} else if (key.type === 'nprofile' && key.data.pubkey) {
|
||||||
|
navigate('Profile', { pubKey: key.data.pubkey })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import { handleInfinityScroll } from '../../Functions/NativeFunctions'
|
|||||||
import { useFocusEffect } from '@react-navigation/native'
|
import { useFocusEffect } from '@react-navigation/native'
|
||||||
import ProfileData from '../../Components/ProfileData'
|
import ProfileData from '../../Components/ProfileData'
|
||||||
import ProfileActions from '../../Components/ProfileActions'
|
import ProfileActions from '../../Components/ProfileActions'
|
||||||
|
import TextContent from '../../Components/TextContent'
|
||||||
|
|
||||||
interface ProfilePageProps {
|
interface ProfilePageProps {
|
||||||
route: { params: { pubKey: string } }
|
route: { params: { pubKey: string } }
|
||||||
@ -170,7 +171,7 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View>
|
<View>
|
||||||
<Text>{user?.about}</Text>
|
<TextContent content={user?.about} showPreview={false} numberOfLines={10} />
|
||||||
</View>
|
</View>
|
||||||
<Divider style={styles.divider} />
|
<Divider style={styles.divider} />
|
||||||
<View style={styles.profileActions}>
|
<View style={styles.profileActions}>
|
||||||
|
@ -219,6 +219,7 @@ export const SendPage: React.FC<SendPageProps> = ({ route }) => {
|
|||||||
setContent((prev) => `${prev}\n\n${imageUri}`)
|
setContent((prev) => `${prev}\n\n${imageUri}`)
|
||||||
setStartUpload(false)
|
setStartUpload(false)
|
||||||
}}
|
}}
|
||||||
|
onError={() => setStartUpload(false)}
|
||||||
uploadingFile={uploadingFile}
|
uploadingFile={uploadingFile}
|
||||||
setUploadingFile={setUploadingFile}
|
setUploadingFile={setUploadingFile}
|
||||||
/>
|
/>
|
||||||
|
@ -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 {
|
export function getNpub(key: string | undefined): string {
|
||||||
if (!key) return ''
|
if (!key) return ''
|
||||||
@ -13,6 +20,21 @@ export function getNpub(key: string | undefined): string {
|
|||||||
return key
|
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 {
|
export function getNevent(key: string | undefined): string {
|
||||||
if (!key) return ''
|
if (!key) return ''
|
||||||
if (isPublicKey(key)) return key
|
if (isPublicKey(key)) return key
|
||||||
|
@ -34,17 +34,49 @@ export interface ResilientAssignation {
|
|||||||
|
|
||||||
export const fallbackRelays = [
|
export const fallbackRelays = [
|
||||||
'wss://brb.io',
|
'wss://brb.io',
|
||||||
'wss://damus.io',
|
|
||||||
'wss://nostr-pub.wellorder.net',
|
'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.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 {
|
class RelayPool {
|
||||||
|
Loading…
Reference in New Issue
Block a user