mirror of
https://github.com/KoalaSat/nostros.git
synced 2024-09-29 22:50:43 +00:00
LndHub
This commit is contained in:
parent
9f120fd44d
commit
6948db384c
@ -237,6 +237,9 @@ public class Database {
|
||||
try {
|
||||
instance.execSQL("ALTER TABLE nostros_relays ADD COLUMN paid INT DEFAULT 0;");
|
||||
} catch (SQLException e) { }
|
||||
try {
|
||||
instance.execSQL("ALTER TABLE nostros_zaps ADD COLUMN preimage TEXT;");
|
||||
} catch (SQLException e) { }
|
||||
}
|
||||
|
||||
public void saveEvent(JSONObject data, String userPubKey, String relayUrl) throws JSONException {
|
||||
|
@ -544,10 +544,12 @@ public class Event {
|
||||
JSONArray eTags = filterTags("e");
|
||||
JSONArray bolt11Tags = filterTags("bolt11");
|
||||
JSONArray descriptionTags = filterTags("description");
|
||||
JSONArray preimageTags = filterTags("preimage");
|
||||
|
||||
String zapped_event_id = "";
|
||||
String zapped_user_id = "";
|
||||
String zapper_user_id = "";
|
||||
String preimage = "";
|
||||
double amount = 0;
|
||||
if (descriptionTags.length() > 0) {
|
||||
JSONArray tag = descriptionTags.getJSONArray(0);
|
||||
@ -564,6 +566,9 @@ public class Event {
|
||||
if (pTags.length() > 0) {
|
||||
zapped_user_id = pTags.getJSONArray(pTags.length() - 1).getString(1);
|
||||
}
|
||||
if (preimageTags.length() > 0) {
|
||||
preimage = preimageTags.getJSONArray(preimageTags.length() - 1).getString(1);
|
||||
}
|
||||
|
||||
String userQuery = "SELECT created_at FROM nostros_users WHERE zap_pubkey = ? AND id = ?";
|
||||
@SuppressLint("Recycle") Cursor userCursor = database.rawQuery(userQuery, new String[] {pubkey, zapped_user_id});
|
||||
@ -581,6 +586,7 @@ public class Event {
|
||||
values.put("zapped_user_id", zapped_user_id);
|
||||
values.put("zapped_event_id", zapped_event_id);
|
||||
values.put("zapper_user_id", zapper_user_id);
|
||||
values.put("preimage", preimage);
|
||||
|
||||
database.insert("nostros_zaps", null, values);
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ import RBSheet from 'react-native-raw-bottom-sheet'
|
||||
import { Card, IconButton, Text, useTheme } from 'react-native-paper'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import { decode, type PaymentRequestObject, type TagsObject } from 'bolt11'
|
||||
import { WalletContext } from '../../Contexts/WalletContext'
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
|
||||
|
||||
interface LnPreviewProps {
|
||||
setOpen?: (open: boolean) => void
|
||||
@ -21,11 +23,13 @@ export const LnPreview: React.FC<LnPreviewProps> = ({
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const { t } = useTranslation('common')
|
||||
const { active, payInvoice } = React.useContext(WalletContext)
|
||||
const { getSatoshiSymbol } = React.useContext(AppContext)
|
||||
const bottomSheetInvoiceRef = React.useRef<RBSheet>(null)
|
||||
const [decodedLnUrl, setDecodedLnUrl] = useState<
|
||||
PaymentRequestObject & { tagsObject: TagsObject }
|
||||
>()
|
||||
const [paymentDone, setPaymentDone] = useState<boolean>()
|
||||
|
||||
useEffect(() => {
|
||||
if (invoice) {
|
||||
@ -43,6 +47,12 @@ export const LnPreview: React.FC<LnPreviewProps> = ({
|
||||
Clipboard.setString(invoice ?? '')
|
||||
}
|
||||
|
||||
const payWithWallet: () => void = () => {
|
||||
if (invoice) {
|
||||
payInvoice(invoice).then(setPaymentDone)
|
||||
}
|
||||
}
|
||||
|
||||
const openApp: () => void = () => {
|
||||
Linking.openURL(`lightning:${invoice}`)
|
||||
}
|
||||
@ -76,7 +86,15 @@ export const LnPreview: React.FC<LnPreviewProps> = ({
|
||||
<Card style={styles.qrContainer}>
|
||||
<Card.Content>
|
||||
<View style={styles.qr}>
|
||||
{paymentDone === undefined ? (
|
||||
<QRCode value={invoice} quietZone={8} size={Dimensions.get('window').width - 64} />
|
||||
) : (
|
||||
<MaterialCommunityIcons
|
||||
name={paymentDone ? 'check-circle-outline' : 'close-circle-outline'}
|
||||
size={120}
|
||||
color={paymentDone ? '#7ADC70' : theme.colors.error}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.qrText}>
|
||||
<Text>{decodedLnUrl?.satoshis} </Text>
|
||||
@ -89,8 +107,14 @@ export const LnPreview: React.FC<LnPreviewProps> = ({
|
||||
<IconButton icon='content-copy' size={28} onPress={copyInvoice} />
|
||||
<Text>{t('lnPayment.copy')}</Text>
|
||||
</View>
|
||||
{active && (
|
||||
<View style={styles.actionButton}>
|
||||
<IconButton icon='wallet' size={28} onPress={openApp} />
|
||||
<IconButton icon='wallet' size={28} onPress={payWithWallet} />
|
||||
<Text>{t('lnPayment.pay')}</Text>
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.actionButton}>
|
||||
<IconButton icon='exit-to-app' size={28} onPress={openApp} />
|
||||
<Text>{t('lnPayment.open')}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
@ -18,10 +18,14 @@ import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityI
|
||||
import { navigate } from '../../lib/Navigation'
|
||||
import { usernamePubKey } from '../../Functions/RelayFunctions/Users'
|
||||
import ProfileData from '../ProfileData'
|
||||
import { WalletContext } from '../../Contexts/WalletContext'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
|
||||
export const MenuItems: React.FC = () => {
|
||||
const [drawerItemIndex, setDrawerItemIndex] = React.useState<number>(-1)
|
||||
const { getSatoshiSymbol } = React.useContext(AppContext)
|
||||
const { relays } = React.useContext(RelayPoolContext)
|
||||
const { balance, active } = React.useContext(WalletContext)
|
||||
const {
|
||||
nPub,
|
||||
publicKey,
|
||||
@ -61,6 +65,8 @@ export const MenuItems: React.FC = () => {
|
||||
navigate('Config')
|
||||
} else if (key === 'contacts') {
|
||||
navigate('Contacts')
|
||||
} else if (key === 'wallet') {
|
||||
navigate('Wallet')
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,7 +120,6 @@ export const MenuItems: React.FC = () => {
|
||||
)}
|
||||
<Drawer.Section>
|
||||
{publicKey && (
|
||||
<>
|
||||
<Drawer.Item
|
||||
label={t('menuItems.relays')}
|
||||
icon={() => (
|
||||
@ -140,7 +145,25 @@ export const MenuItems: React.FC = () => {
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
)}
|
||||
<Drawer.Item
|
||||
label={t('menuItems.wallet')}
|
||||
icon='wallet-outline'
|
||||
key='wallet'
|
||||
active={drawerItemIndex === 1}
|
||||
onPress={() => onPressItem('wallet', 1)}
|
||||
onTouchEnd={() => setDrawerItemIndex(-1)}
|
||||
right={() => {
|
||||
if (!active) return <></>
|
||||
return (
|
||||
<Text>
|
||||
{`${balance} `}
|
||||
{getSatoshiSymbol()}
|
||||
</Text>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
{publicKey && (
|
||||
<Drawer.Item
|
||||
label={t('menuItems.contacts')}
|
||||
icon='contacts-outline'
|
||||
@ -149,14 +172,13 @@ export const MenuItems: React.FC = () => {
|
||||
onPress={() => onPressItem('contacts', 1)}
|
||||
onTouchEnd={() => setDrawerItemIndex(-1)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Drawer.Item
|
||||
label={t('menuItems.configuration')}
|
||||
icon='cog'
|
||||
key='configuration'
|
||||
active={drawerItemIndex === 1}
|
||||
onPress={() => onPressItem('config', 1)}
|
||||
active={drawerItemIndex === 2}
|
||||
onPress={() => onPressItem('config', 2)}
|
||||
onTouchEnd={() => setDrawerItemIndex(-1)}
|
||||
/>
|
||||
</Drawer.Section>
|
||||
@ -165,16 +187,16 @@ export const MenuItems: React.FC = () => {
|
||||
label={t('menuItems.about')}
|
||||
icon='information-outline'
|
||||
key='about'
|
||||
active={drawerItemIndex === 2}
|
||||
onPress={() => onPressItem('about', 2)}
|
||||
active={drawerItemIndex === 3}
|
||||
onPress={() => onPressItem('about', 3)}
|
||||
onTouchEnd={() => setDrawerItemIndex(-1)}
|
||||
/>
|
||||
<Drawer.Item
|
||||
label={t('menuItems.faq')}
|
||||
icon='comment-question-outline'
|
||||
key='faq'
|
||||
active={drawerItemIndex === 2}
|
||||
onPress={() => onPressItem('faq', 2)}
|
||||
active={drawerItemIndex === 4}
|
||||
onPress={() => onPressItem('faq', 4)}
|
||||
onTouchEnd={() => setDrawerItemIndex(-1)}
|
||||
/>
|
||||
<Drawer.Item
|
||||
|
@ -13,7 +13,7 @@ interface ProfileCardProps {
|
||||
publicKey?: string
|
||||
lnurl?: string
|
||||
lnAddress?: string
|
||||
validNip05?: number | undefined
|
||||
validNip05?: number | boolean | undefined
|
||||
nip05?: string
|
||||
picture?: string
|
||||
avatarSize?: number
|
||||
|
208
frontend/Contexts/WalletContext.tsx
Normal file
208
frontend/Contexts/WalletContext.tsx
Normal file
@ -0,0 +1,208 @@
|
||||
import axios from 'axios'
|
||||
import { getUnixTime } from 'date-fns'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import SInfo from 'react-native-sensitive-info'
|
||||
|
||||
export interface LndHub {
|
||||
accessToken: string
|
||||
refreshToken: string
|
||||
url: string
|
||||
}
|
||||
|
||||
export interface WalletAction {
|
||||
id: string
|
||||
monto: number
|
||||
type: 'invoice' | 'transaction'
|
||||
description: string
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
export interface WalletContextProps {
|
||||
active: boolean
|
||||
updatedAt?: number
|
||||
lndHub?: LndHub
|
||||
setLndHub: (lndHub: LndHub) => void
|
||||
balance?: number
|
||||
transactions: WalletAction[]
|
||||
invoices: WalletAction[]
|
||||
refreshLndHub: (login?: string, password?: string, uri?: string) => void
|
||||
payInvoice: (invoice: string) => Promise<boolean>
|
||||
logoutWallet: () => void
|
||||
}
|
||||
|
||||
export interface WalletContextProviderProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export const initialWalletContext: WalletContextProps = {
|
||||
active: false,
|
||||
setLndHub: () => {},
|
||||
transactions: [],
|
||||
invoices: [],
|
||||
refreshLndHub: () => {},
|
||||
payInvoice: async () => false,
|
||||
logoutWallet: () => {},
|
||||
}
|
||||
|
||||
export const WalletContextProvider = ({ children }: WalletContextProviderProps): JSX.Element => {
|
||||
const [active, setActive] = React.useState<boolean>(initialWalletContext.active)
|
||||
const [lndHub, setLndHub] = useState<LndHub>()
|
||||
const [balance, setBalance] = useState<number>()
|
||||
const [updatedAt, setUpdatedAt] = useState<string>()
|
||||
const [transactions, setTransactions] = useState<WalletAction[]>(
|
||||
initialWalletContext.transactions,
|
||||
)
|
||||
const [invoices, setInvoices] = useState<WalletAction[]>(initialWalletContext.invoices)
|
||||
|
||||
useEffect(() => {
|
||||
SInfo.getItem('lndHub', {}).then((value) => {
|
||||
if (value) {
|
||||
setLndHub(JSON.parse(value))
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
const refreshLndHub: (login?: string, password?: string, uri?: string) => void = (
|
||||
login,
|
||||
password,
|
||||
uri,
|
||||
) => {
|
||||
setLndHub(undefined)
|
||||
let params:
|
||||
| { type: string; refresh_token?: string; login?: string; password?: string }
|
||||
| undefined
|
||||
if (lndHub?.refreshToken) {
|
||||
params = {
|
||||
type: 'refresh_token',
|
||||
refresh_token: lndHub?.refreshToken,
|
||||
}
|
||||
uri = lndHub?.url
|
||||
} else if (login !== '' && password !== '' && uri !== '') {
|
||||
params = {
|
||||
type: 'auth',
|
||||
login,
|
||||
password,
|
||||
}
|
||||
}
|
||||
if (params && uri) {
|
||||
axios.post(`${uri}/auth`, {}, { params }).then((response) => {
|
||||
if (response?.data?.refresh_token && response.data?.access_token && uri) {
|
||||
setLndHub({
|
||||
accessToken: response.data.access_token,
|
||||
refreshToken: response.data.refresh_token,
|
||||
url: uri,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const updateLndHub: () => void = () => {
|
||||
if (!lndHub) return
|
||||
|
||||
const headers = {
|
||||
Authorization: `Bearer ${lndHub.accessToken}`,
|
||||
}
|
||||
|
||||
axios.get(`${lndHub.url}/balance`, { headers }).then((response) => {
|
||||
if (response) {
|
||||
if (response.status === 200) {
|
||||
setUpdatedAt(`${getUnixTime(new Date())}-balance`)
|
||||
setBalance(response.data?.BTC?.AvailableBalance ?? 0)
|
||||
SInfo.setItem('lndHub', JSON.stringify(lndHub), {})
|
||||
setActive(true)
|
||||
} else if (response.status === 401) {
|
||||
refreshLndHub()
|
||||
}
|
||||
}
|
||||
})
|
||||
axios.get(`${lndHub.url}/gettxs`, { headers }).then((response) => {
|
||||
if (response) {
|
||||
setTransactions(
|
||||
response.data.map((item: any) => {
|
||||
return {
|
||||
id: item.payment_preimage,
|
||||
monto: item.value,
|
||||
type: 'transaction',
|
||||
description: item.memo,
|
||||
timestamp: item.timestamp,
|
||||
}
|
||||
}),
|
||||
)
|
||||
setUpdatedAt(`${getUnixTime(new Date())}-gettxs`)
|
||||
}
|
||||
})
|
||||
axios.get(`${lndHub.url}/getuserinvoices`, { headers }).then((response) => {
|
||||
if (response) {
|
||||
setInvoices(
|
||||
response.data
|
||||
.filter((item: any) => item.ispaid)
|
||||
.map((item: any) => {
|
||||
return {
|
||||
id: item.payment_hash,
|
||||
monto: item.amt,
|
||||
type: 'invoice',
|
||||
description: item.description,
|
||||
timestamp: item.timestamp,
|
||||
}
|
||||
}),
|
||||
)
|
||||
setUpdatedAt(`${getUnixTime(new Date())}-getuserinvoices`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(updateLndHub, [lndHub])
|
||||
|
||||
const payInvoice: (invoice: string) => Promise<boolean> = async (invoice) => {
|
||||
if (active && invoice && invoice !== '') {
|
||||
const headers = {
|
||||
Authorization: `Bearer ${lndHub?.accessToken}`,
|
||||
}
|
||||
const params = {
|
||||
invoice,
|
||||
}
|
||||
const response = await axios.post(`${lndHub?.url}/payinvoice`, params, { headers })
|
||||
if (response) {
|
||||
if (response.status === 200) {
|
||||
updateLndHub()
|
||||
return response?.data?.payment_error === ''
|
||||
} else if (response.status === 401) {
|
||||
refreshLndHub()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
const logoutWallet: () => void = () => {
|
||||
SInfo.deleteItem('lndHub', {})
|
||||
setActive(false)
|
||||
setLndHub(undefined)
|
||||
setBalance(undefined)
|
||||
setUpdatedAt(undefined)
|
||||
setTransactions([])
|
||||
setInvoices([])
|
||||
}
|
||||
|
||||
return (
|
||||
<WalletContext.Provider
|
||||
value={{
|
||||
active,
|
||||
updatedAt,
|
||||
setLndHub,
|
||||
balance,
|
||||
transactions,
|
||||
invoices,
|
||||
refreshLndHub,
|
||||
payInvoice,
|
||||
logoutWallet,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</WalletContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const WalletContext = React.createContext(initialWalletContext)
|
@ -14,6 +14,7 @@ export interface Zap extends Event {
|
||||
nip05: string
|
||||
lnurl: string
|
||||
ln_address: string
|
||||
preimage: string
|
||||
}
|
||||
|
||||
const databaseToEntity: (object: any) => Zap = (object) => {
|
||||
@ -109,7 +110,7 @@ export const getUserZaps: (
|
||||
|
||||
export const getZaps: (
|
||||
db: QuickSQLiteConnection,
|
||||
filters: { eventId?: string; zapperId?: string; limit?: number },
|
||||
filters: { eventId?: string; zapperId?: string; limit?: number; preimages?: string[] },
|
||||
) => Promise<Zap[]> = async (db, filters) => {
|
||||
let groupsQuery = `
|
||||
SELECT
|
||||
@ -121,6 +122,12 @@ export const getZaps: (
|
||||
nostros_users ON nostros_users.id = nostros_zaps.zapper_user_id
|
||||
`
|
||||
|
||||
if (filters.preimages) {
|
||||
groupsQuery += `
|
||||
WHERE preimage IN ("${filters.preimages.join('", "')}")
|
||||
`
|
||||
}
|
||||
|
||||
if (filters.eventId) {
|
||||
groupsQuery += `
|
||||
WHERE zapped_event_id = "${filters.eventId}"
|
||||
|
@ -2,9 +2,10 @@
|
||||
"common": {
|
||||
"time": {
|
||||
"today": "Today",
|
||||
"yerterday": "Yesterday"
|
||||
"yesterday": "Yesterday"
|
||||
},
|
||||
"drawers": {
|
||||
"walletLogout": "Logout",
|
||||
"relaysTitle": "Relays",
|
||||
"relaysDescription": "Relays sind Nodes (Netzknoten) im Netzwerk, die als Vermittler von Nachrichten zwischen den Anwendungen dienen.\n\n\nRelays können die Belastbarkeit und die Verfügbarkeit des Netzwerks verbessern, indem sie dafür sorgen, das Nachrichten trotz Unterbrechungen oder Ausfällen der Verfügbarkeit ausgeliefert werden.\n\n\nRelays können Privatsphäre und Netzwerksicherheit erhöhen, indem sie Aufenthaltsort und Identität der Anwendungen bzw. der Benutzer, die miteinander kommunizieren, verbergen\n\n\nDies kann von Wert sein in Umgebungen, in denen Zensur oder Überwachung ein Problem ist.\n\n\nEs ist wichtig darauf hinzuweisen, das Relays für schadhafte Zwecke missbraucht werden können, wie zum Beispiel Sniffing oder das Zensieren von Netzwerkverkehr.\n\n\nDeswegen ist es von Bedeutung, die Nutzung von Relays sorgfältig abzuwägen, und angemessenene Sichheitsmassnahmen anzuwenden, um Identität, Privatsphäre und Netzwerksicherheit zu schützen.",
|
||||
"keysTitle": "Was sind Schlüssel?",
|
||||
@ -28,6 +29,7 @@
|
||||
"homeNavigator": {
|
||||
"ProfileCreate": "Profil anlegen",
|
||||
"Search": "",
|
||||
"Wallet": "Wallet",
|
||||
"ImageGallery": "",
|
||||
"ProfileConnect": "",
|
||||
"Group": "",
|
||||
@ -172,7 +174,8 @@
|
||||
"copy": "Kopieren",
|
||||
"open": "Öffne Wallet",
|
||||
"anonTip": "Anonymer Tip",
|
||||
"zap": "Zap"
|
||||
"zap": "Zap",
|
||||
"pay": "Pay"
|
||||
},
|
||||
"notificationsFeed": {
|
||||
"connectContactRelays": "Reconnect to contacts' relays",
|
||||
|
@ -2,9 +2,10 @@
|
||||
"common": {
|
||||
"time": {
|
||||
"today": "Today",
|
||||
"yerterday": "Yesterday"
|
||||
"yesterday": "Yesterday"
|
||||
},
|
||||
"drawers": {
|
||||
"walletLogout": "Logout",
|
||||
"relaysTitle": "Relays",
|
||||
"relaysDescription": "Relays are nodes on the network that act as intermediaries for the transmission of messages between applications.\n\n\nRelays can be used to improve network resiliency and availability by allowing messages to be delivered even when there are failures or interruptions in connectivity.\n\n\nRelays can also be used to improve privacy and network security, as they can hide the location and identity of applications that communicate with each other. This can be useful in environments where censorship or surveillance is an issue.\n\n\nIt is important to note that relays can also be used for malicious purposes, such as sniffing or censoring network traffic.\n\n\nTherefore, it is important to carefully evaluate the use of relays and consider appropriate security measures to protect privacy and network security.",
|
||||
"keysTitle": "What are these keys?",
|
||||
@ -25,9 +26,15 @@
|
||||
"loginStep3Description": "You can add the relays used by your contacts to your list and connect to them to strengthen the network.",
|
||||
"loginskip": "You can skip this process but you may miss out on the opportunity to connect to a diverse range of relays and expand your network's reach"
|
||||
},
|
||||
"walletPage": {
|
||||
"addLnhub": "Add LNHub",
|
||||
"lnHub": "LNHub address",
|
||||
"connect": "Connect"
|
||||
},
|
||||
"homeNavigator": {
|
||||
"ProfileCreate": "Create profile",
|
||||
"Search": "",
|
||||
"Wallet": "Wallet",
|
||||
"ImageGallery": "",
|
||||
"ProfileConnect": "",
|
||||
"Group": "",
|
||||
@ -106,6 +113,7 @@
|
||||
"poweredBy": "Powered by {{uri}}"
|
||||
},
|
||||
"menuItems": {
|
||||
"wallet": "Wallet",
|
||||
"relays": "Relays",
|
||||
"notConnected": "Not connected",
|
||||
"connectedRelays": "{{number}} connected",
|
||||
@ -175,7 +183,8 @@
|
||||
"copy": "Copy",
|
||||
"open": "Open wallet",
|
||||
"anonTip": "Anonymous tip",
|
||||
"zap": "Zap"
|
||||
"zap": "Zap",
|
||||
"pay": "Pay"
|
||||
},
|
||||
"notificationsFeed": {
|
||||
"reposted": "Reposted",
|
||||
|
@ -2,9 +2,10 @@
|
||||
"common": {
|
||||
"time": {
|
||||
"today": "Hoy",
|
||||
"yerterday": "Ayer"
|
||||
"yesterday": "Ayer"
|
||||
},
|
||||
"drawers": {
|
||||
"walletLogout": "Logout",
|
||||
"relaysTitle": "Relés",
|
||||
"relaysDescription": "Los relays son nodos en la red que actúan como intermediarios para la transmisión demensajes entre aplicaciones.\n\n\nLos relays pueden ser utilizados para mejorar la resiliencia yla disponibilidad de la red, ya que permiten que los mensajes sean entregados aun cuandohay fallos o interrupciones en la conectividad.\n\n\nLos relays también pueden ser utilizadospara mejorar la privacidad y la seguridad de la red, ya que pueden ocultar la ubicación yel identidad de las aplicaciones que se comunican entre sí a través de ellos. Esto puedeser útil en entornos donde la censura o la vigilancia son un problema.\n\n\nEs importante teneren cuenta que los relays también pueden ser utilizados para propósitos malintencionados,como para rastrear o censurar el tráfico de la red.\n\n\nPor lo tanto, es importante evaluarcuidadosamente el uso de relays y considerar medidas de seguridad adecuadas para protegerla privacidad y la seguridad de la red.",
|
||||
"keysTitle": "¿Qué son las claves?",
|
||||
@ -39,6 +40,7 @@
|
||||
"Group": "",
|
||||
"QrReader": "",
|
||||
"Search": "",
|
||||
"Wallet": "Wallet",
|
||||
"ImageGallery": "",
|
||||
"ProfileCreate": "Crear perfil",
|
||||
"ProfileConnect": "",
|
||||
@ -188,7 +190,8 @@
|
||||
"copy": "Copiar",
|
||||
"open": "Abrir wallet",
|
||||
"anonTip": "Propina anónima",
|
||||
"zap": "Zap"
|
||||
"zap": "Zap",
|
||||
"pay": "Pagar"
|
||||
},
|
||||
"notificationsFeed": {
|
||||
"reposted": "Reposteado",
|
||||
|
@ -2,9 +2,10 @@
|
||||
"common": {
|
||||
"time": {
|
||||
"today": "Today",
|
||||
"yerterday": "Yesterday"
|
||||
"yesterday": "Yesterday"
|
||||
},
|
||||
"drawers": {
|
||||
"walletLogout": "Logout",
|
||||
"relaysTitle": "Relais",
|
||||
"relaysDescription": "Les relais sont des nœuds du réseau qui servent d'intermédiaires pour la transmission de messages entre les applications.\n\nLes relais peuvent être utilisés pour améliorer la résilience et la disponibilité des réseaux en permettant la transmission des messages même en cas de défaillance ou d'interruption de la connectivité.\n\nLes relais peuvent également être utilisés pour améliorer la confidentialité et la sécurité des réseaux, car ils peuvent cacher l'emplacement et l'identité des applications qui communiquent entre elles par leur intermédiaire. Cela peut être utile dans les environnements où la censure ou la surveillance est un problème.\n\nIl est important de noter que les relais peuvent également être utilisés à des fins malveillantes, par exemple pour suivre ou censurer le trafic réseau.\n\nIl est donc important d'évaluer soigneusement l'utilisation des relais et d'envisager des mesures de sécurité appropriées pour protéger la vie privée et la sécurité du réseau.",
|
||||
"keysTitle": "C'est quoi les clés ?",
|
||||
@ -39,6 +40,7 @@
|
||||
"Group": "",
|
||||
"QrReader": "",
|
||||
"ImageGallery": "",
|
||||
"Wallet": "Wallet",
|
||||
"Search": "",
|
||||
"ProfileCreate": "Create profile",
|
||||
"ProfileConnect": "",
|
||||
@ -193,7 +195,8 @@
|
||||
"copy": "Copier",
|
||||
"open": "Ouvrir le wallet",
|
||||
"anonTip": "Anonymous tip",
|
||||
"zap": "Zap"
|
||||
"zap": "Zap",
|
||||
"pay": "Pay"
|
||||
},
|
||||
"notificationsFeed": {
|
||||
"reposted": "Reposted",
|
||||
|
@ -2,9 +2,10 @@
|
||||
"common": {
|
||||
"time": {
|
||||
"today": "Today",
|
||||
"yerterday": "Yesterday"
|
||||
"yesterday": "Yesterday"
|
||||
},
|
||||
"drawers": {
|
||||
"walletLogout": "Logout",
|
||||
"relaysTitle": "Реле",
|
||||
"relaysDescription": "Реле(ретрансляторы) — это сетевые узлы, которые работают как посредники для передачи сообщений между приложениями.\n\n\nРетрансляторы можно использовать для повышения отказоустойчивости и доступности сети, позволяя доставлять сообщения даже при сбоях и проблемах с подключением. n\n\nРеле также можно использовать для повышения конфиденциальности и сетевой безопасности, поскольку они могут скрывать местоположение и идентификационные данные приложений, которые обмениваются данными. Это может быть полезно в условиях цензуры или слежки.\n\n \nВажно отметить, что реле также могут использоваться в злонамеренных целях, таких как прослушивание или цензура сетевого трафика.\n \n\nПоэтому важно тщательно оценивать использование реле и принимать соответствующие меры безопасности для защиты конфиденциальности и сетевой безопасности.",
|
||||
"keysTitle": "Что такое ключи?",
|
||||
@ -38,6 +39,7 @@
|
||||
"homeNavigator": {
|
||||
"Group": "",
|
||||
"QrReader": "",
|
||||
"Wallet": "Wallet",
|
||||
"ImageGallery": "",
|
||||
"Search": "",
|
||||
"ProfileCreate": "Create profile",
|
||||
@ -189,7 +191,8 @@
|
||||
"copy": "Скопировать",
|
||||
"open": "Открыть кошелек",
|
||||
"anonTip": "Anonymous tip",
|
||||
"zap": "Zap"
|
||||
"zap": "Zap",
|
||||
"pay": "Pay"
|
||||
},
|
||||
"notificationsFeed": {
|
||||
"reposted": "Reposted",
|
||||
|
@ -2,9 +2,10 @@
|
||||
"common": {
|
||||
"time": {
|
||||
"today": "Today",
|
||||
"yerterday": "Yesterday"
|
||||
"yesterday": "Yesterday"
|
||||
},
|
||||
"drawers": {
|
||||
"walletLogout": "Logout",
|
||||
"relaysTitle": "关于中继",
|
||||
"relaysDescription": "中继是网络上的节点,作为应用程序之间传输消息的中介。\n\n\n中继可用于提高网络的弹性和可用性,即使在连接出现故障或中断的情况下,也能传递消息。 \n\n\n中继还可用于提高隐私和网络安全,因为它们可以隐藏相互通信的用户的位置和身份。 \n\n\n这在审查或监视是一个问题的环境中是很有用的。 \n\n\n需要注意的是,中继也可以用于恶意的目的,如嗅探或审查网络流量。",
|
||||
"keysTitle": "这些密钥是什么?",
|
||||
@ -40,6 +41,7 @@
|
||||
"QrReader": "",
|
||||
"ImageGallery": "",
|
||||
"Search": "",
|
||||
"Wallet": "Wallet",
|
||||
"ProfileCreate": "创建用户",
|
||||
"ProfileConnect": "",
|
||||
"Contacts": "联系人",
|
||||
@ -187,7 +189,8 @@
|
||||
"copy": "复制",
|
||||
"open": "打开钱包",
|
||||
"anonTip": "匿名赞赏",
|
||||
"zap": "赞赏"
|
||||
"zap": "赞赏",
|
||||
"pay": "Pay"
|
||||
},
|
||||
"notificationsFeed": {
|
||||
"reposted": "Reposted",
|
||||
|
@ -2,7 +2,7 @@ import * as React from 'react'
|
||||
import { Platform, View } from 'react-native'
|
||||
import type { DrawerNavigationProp } from '@react-navigation/drawer'
|
||||
import { CardStyleInterpolators, createStackNavigator } from '@react-navigation/stack'
|
||||
import { Appbar, Text, useTheme } from 'react-native-paper'
|
||||
import { Appbar, Button, Text, useTheme } from 'react-native-paper'
|
||||
import RBSheet from 'react-native-raw-bottom-sheet'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import HomePage from '../HomePage'
|
||||
@ -29,11 +29,14 @@ import DatabaseModule from '../../lib/Native/DatabaseModule'
|
||||
import ImageGalleryPage from '../ImageGalleryPage'
|
||||
import { navigate } from '../../lib/Navigation'
|
||||
import SearchPage from '../SearchPage'
|
||||
import WalletPage from '../WalletPage'
|
||||
import { WalletContext } from '../../Contexts/WalletContext'
|
||||
|
||||
export const HomeNavigator: React.FC = () => {
|
||||
const theme = useTheme()
|
||||
const { t } = useTranslation('common')
|
||||
const { displayRelayDrawer, setDisplayrelayDrawer } = React.useContext(RelayPoolContext)
|
||||
const { logoutWallet } = React.useContext(WalletContext)
|
||||
const {
|
||||
displayUserDrawer,
|
||||
setDisplayNoteDrawer,
|
||||
@ -46,6 +49,7 @@ export const HomeNavigator: React.FC = () => {
|
||||
const bottomSheetProfileRef = React.useRef<RBSheet>(null)
|
||||
const bottomSheetNoteRef = React.useRef<RBSheet>(null)
|
||||
const bottomSheetRelayRef = React.useRef<RBSheet>(null)
|
||||
const bottomWalletRef = React.useRef<RBSheet>(null)
|
||||
const Stack = React.useMemo(() => createStackNavigator(), [])
|
||||
const cardStyleInterpolator = React.useMemo(
|
||||
() =>
|
||||
@ -131,6 +135,12 @@ export const HomeNavigator: React.FC = () => {
|
||||
route.params?.title ? route.params?.title : t(`homeNavigator.${route.name}`)
|
||||
}
|
||||
/>
|
||||
{['Wallet'].includes(route.name) && (
|
||||
<Appbar.Action
|
||||
icon='dots-vertical'
|
||||
onPress={() => bottomWalletRef.current?.open()}
|
||||
/>
|
||||
)}
|
||||
{['Profile', 'Conversation'].includes(route.name) && (
|
||||
<Appbar.Action
|
||||
icon='dots-vertical'
|
||||
@ -187,6 +197,7 @@ export const HomeNavigator: React.FC = () => {
|
||||
<Stack.Screen name='Search' component={SearchPage} />
|
||||
</Stack.Group>
|
||||
<Stack.Group>
|
||||
<Stack.Screen name='Wallet' component={WalletPage} />
|
||||
<Stack.Screen name='Contacts' component={ContactsPage} />
|
||||
<Stack.Screen name='Relays' component={RelaysPage} />
|
||||
<Stack.Screen name='About' component={AboutPage} />
|
||||
@ -228,6 +239,19 @@ export const HomeNavigator: React.FC = () => {
|
||||
<Text variant='bodyMedium'>{t('drawers.relaysDescription')}</Text>
|
||||
</View>
|
||||
</RBSheet>
|
||||
<RBSheet ref={bottomWalletRef} closeOnDragDown={true} customStyles={bottomSheetStyles}>
|
||||
<View>
|
||||
<Button
|
||||
mode='contained'
|
||||
onPress={() => {
|
||||
logoutWallet()
|
||||
bottomWalletRef.current?.close()
|
||||
}}
|
||||
>
|
||||
{t('drawers.walletLogout')}
|
||||
</Button>
|
||||
</View>
|
||||
</RBSheet>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -38,7 +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 { handleInfinityScroll } from '../../../Functions/NativeFunctions'
|
||||
import { formatHour, handleInfinityScroll } from '../../../Functions/NativeFunctions'
|
||||
|
||||
export const ConversationsFeed: React.FC = () => {
|
||||
const initialPageSize = 14
|
||||
@ -154,7 +154,7 @@ export const ConversationsFeed: React.FC = () => {
|
||||
</View>
|
||||
<View style={styles.contactInfo}>
|
||||
<View style={styles.contactDate}>
|
||||
<Text>{formatHour(item?.created_at, false)}</Text>
|
||||
<Text>{formatHour(item?.created_at)}</Text>
|
||||
{item.pubkey !== publicKey && !item.read && <Badge size={16}></Badge>}
|
||||
</View>
|
||||
</View>
|
||||
|
@ -130,6 +130,7 @@ export const ThirdStep: React.FC<ThirdStepProps> = ({ nextStep, skip }) => {
|
||||
data={asignation}
|
||||
renderItem={renderItem}
|
||||
ItemSeparatorComponent={Divider}
|
||||
style={styles.list}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
@ -182,6 +183,9 @@ const styles = StyleSheet.create({
|
||||
relayColor: {
|
||||
paddingTop: 9,
|
||||
},
|
||||
list: {
|
||||
maxHeight: 230,
|
||||
},
|
||||
})
|
||||
|
||||
export default ThirdStep
|
||||
|
308
frontend/Pages/WalletPage/index.tsx
Normal file
308
frontend/Pages/WalletPage/index.tsx
Normal file
@ -0,0 +1,308 @@
|
||||
import Clipboard from '@react-native-clipboard/clipboard'
|
||||
import { differenceInDays, format, fromUnixTime, isSameDay } from 'date-fns'
|
||||
import { t } from 'i18next'
|
||||
import React, { useEffect, useMemo } from 'react'
|
||||
import { type ListRenderItem, StyleSheet, View } from 'react-native'
|
||||
import { FlatList } from 'react-native-gesture-handler'
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
Snackbar,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableRipple,
|
||||
useTheme,
|
||||
} from 'react-native-paper'
|
||||
import RBSheet from 'react-native-raw-bottom-sheet'
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
|
||||
import NostrosAvatar from '../../Components/NostrosAvatar'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import { type WalletAction, WalletContext } from '../../Contexts/WalletContext'
|
||||
import { getZaps, type Zap } from '../../Functions/DatabaseFunctions/Zaps'
|
||||
import { navigate } from '../../lib/Navigation'
|
||||
|
||||
export const WalletPage: React.FC = () => {
|
||||
const theme = useTheme()
|
||||
const { getSatoshiSymbol, database, setDisplayUserDrawer } = React.useContext(AppContext)
|
||||
const { refreshLndHub, active, balance, transactions, invoices, updatedAt } =
|
||||
React.useContext(WalletContext)
|
||||
const [lnHubAddress, setLndHubAddress] = React.useState<string>()
|
||||
const [showNotification, setShowNotification] = React.useState<undefined | string>()
|
||||
const [actions, setActions] = React.useState<WalletAction[]>([])
|
||||
const [zaps, setZaps] = React.useState<Record<string, Zap>>({})
|
||||
const bottomLndHubRef = React.useRef<RBSheet>(null)
|
||||
|
||||
useEffect(refreshLndHub, [])
|
||||
useEffect(() => {
|
||||
const array = [...transactions, ...invoices].sort(
|
||||
(item1, item2) => item2.timestamp - item1.timestamp,
|
||||
)
|
||||
setActions(array)
|
||||
if (database) {
|
||||
getZaps(database, { preimages: array.map((item) => item.id) }).then((results) => {
|
||||
if (results) {
|
||||
const map: Record<string, Zap> = {}
|
||||
results.forEach((zap) => {
|
||||
map[zap.preimage] = zap
|
||||
})
|
||||
setZaps(map)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [updatedAt])
|
||||
|
||||
const pasteLndHub: () => void = () => {
|
||||
Clipboard.getString().then((value) => {
|
||||
setLndHubAddress(value ?? '')
|
||||
})
|
||||
}
|
||||
|
||||
const connectLndHub: () => void = () => {
|
||||
const lnHubRegExp = /^lndhub:\/\/(\S*):(\S*)@(\S*)$/gi
|
||||
if (lnHubAddress) {
|
||||
const match = [...lnHubAddress.matchAll(lnHubRegExp)]
|
||||
if (match.length > 0) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
let [_full, login, password, uri] = match[0]
|
||||
if (uri[uri.length - 1] === '/') {
|
||||
uri = uri.substring(0, uri.length - 1)
|
||||
}
|
||||
refreshLndHub(login, password, uri)
|
||||
setLndHubAddress(undefined)
|
||||
bottomLndHubRef.current?.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bottomSheetStyles = React.useMemo(() => {
|
||||
return {
|
||||
container: {
|
||||
backgroundColor: theme.colors.background,
|
||||
paddingTop: 16,
|
||||
paddingRight: 16,
|
||||
paddingBottom: 32,
|
||||
paddingLeft: 16,
|
||||
borderTopRightRadius: 28,
|
||||
borderTopLeftRadius: 28,
|
||||
height: 'auto',
|
||||
},
|
||||
}
|
||||
}, [])
|
||||
|
||||
const login = useMemo(
|
||||
() => (
|
||||
<View style={styles.center}>
|
||||
<Button mode='contained' onPress={() => bottomLndHubRef.current?.open()}>
|
||||
{t('walletPage.addLnhub')}
|
||||
</Button>
|
||||
</View>
|
||||
),
|
||||
[],
|
||||
)
|
||||
|
||||
const renderAction: ListRenderItem<WalletAction> = ({ item, index }) => {
|
||||
const date = fromUnixTime(item.timestamp)
|
||||
const prevDate = index > 0 ? fromUnixTime(actions[index - 1].timestamp) : new Date()
|
||||
|
||||
const formatPattern = differenceInDays(new Date(), date) < 7 ? 'EEEE' : 'MM-dd-yy'
|
||||
|
||||
const zap = zaps[item.id]
|
||||
|
||||
return (
|
||||
<>
|
||||
{(index === 0 || !isSameDay(date, prevDate)) && (
|
||||
<Text variant='titleMedium'>{format(date, formatPattern)}</Text>
|
||||
)}
|
||||
<TouchableRipple
|
||||
onPress={() => {
|
||||
if (zap) {
|
||||
if (zap.zapped_event_id) {
|
||||
navigate('Note', { noteId: zap.zapped_event_id })
|
||||
} else if (zap.zapper_user_id) {
|
||||
setDisplayUserDrawer(zap.zapper_user_id)
|
||||
}
|
||||
}
|
||||
}}
|
||||
disabled={!zap}
|
||||
>
|
||||
<View style={styles.listItem}>
|
||||
<View style={styles.listAvatar}>
|
||||
{zap ? (
|
||||
<NostrosAvatar
|
||||
name={zap.name}
|
||||
pubKey={zap.user_id}
|
||||
src={zap.picture}
|
||||
lnurl={zap.lnurl}
|
||||
lnAddress={zap.ln_address}
|
||||
size={36}
|
||||
/>
|
||||
) : (
|
||||
<Avatar.Text size={36} label='?' />
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.listItemSection}>
|
||||
<View style={styles.row}>
|
||||
<View style={styles.listData}>
|
||||
<View style={styles.row}>
|
||||
<Text>
|
||||
{item.type === 'transaction' && '-'}
|
||||
{`${item.monto} `}
|
||||
</Text>
|
||||
<Text style={styles.listItemSymbol}>{getSatoshiSymbol()}</Text>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<Text>{format(date, 'HH:mm')}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<Text style={{ color: theme.colors.onSurfaceVariant }}>{item.description}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.listIcon}>
|
||||
{item.type === 'transaction' ? (
|
||||
<MaterialCommunityIcons
|
||||
name='arrow-top-right'
|
||||
size={25}
|
||||
color={theme.colors.error}
|
||||
/>
|
||||
) : (
|
||||
<MaterialCommunityIcons name='arrow-bottom-left' size={25} color='#7ADC70' />
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{active ? (
|
||||
<View>
|
||||
<View style={[styles.balance, { backgroundColor: theme.colors.onSecondary }]}>
|
||||
<View style={styles.balanceNumber}>
|
||||
<Text variant='displayMedium'>{`${balance} `}</Text>
|
||||
<Text style={styles.balanceSymbol} variant='headlineSmall'>
|
||||
{getSatoshiSymbol()}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<FlatList
|
||||
data={actions}
|
||||
renderItem={renderAction}
|
||||
style={styles.list}
|
||||
keyExtractor={(item) => item.id}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
login
|
||||
)}
|
||||
{showNotification && (
|
||||
<Snackbar
|
||||
style={styles.snackbar}
|
||||
visible={showNotification !== undefined}
|
||||
duration={Snackbar.DURATION_SHORT}
|
||||
onIconPress={() => setShowNotification(undefined)}
|
||||
onDismiss={() => setShowNotification(undefined)}
|
||||
>
|
||||
{t(`profileCard.notifications.${showNotification}`)}
|
||||
</Snackbar>
|
||||
)}
|
||||
<RBSheet ref={bottomLndHubRef} closeOnDragDown={true} customStyles={bottomSheetStyles}>
|
||||
<View>
|
||||
<Text variant='headlineSmall' style={styles.drawerParagraph}>
|
||||
{t('walletPage.addLnhub')}
|
||||
</Text>
|
||||
<TextInput
|
||||
style={styles.drawerParagraph}
|
||||
mode='outlined'
|
||||
multiline
|
||||
label={t('walletPage.lnHub') ?? ''}
|
||||
onChangeText={setLndHubAddress}
|
||||
value={lnHubAddress}
|
||||
right={
|
||||
<TextInput.Icon
|
||||
icon='content-paste'
|
||||
onPress={pasteLndHub}
|
||||
forceTextInputFocus={false}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Button mode='contained' onPress={connectLndHub} disabled={lnHubAddress === undefined}>
|
||||
{t('walletPage.connect')}
|
||||
</Button>
|
||||
</View>
|
||||
</RBSheet>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
justifyContent: 'space-between',
|
||||
alignContent: 'center',
|
||||
},
|
||||
center: {
|
||||
justifyContent: 'center',
|
||||
alignContent: 'center',
|
||||
height: '100%',
|
||||
padding: 16,
|
||||
},
|
||||
drawerParagraph: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
snackbar: {
|
||||
marginBottom: 85,
|
||||
flex: 1,
|
||||
},
|
||||
balance: {
|
||||
height: 180,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
balanceNumber: {
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
balanceSymbol: {
|
||||
paddingTop: 18,
|
||||
},
|
||||
list: {
|
||||
paddingLeft: 16,
|
||||
paddingTop: 16,
|
||||
},
|
||||
listItem: {
|
||||
paddingTop: 16,
|
||||
paddingBottom: 16,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
listAvatar: {
|
||||
width: '15%',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
listIcon: {
|
||||
width: '10%',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
listItemSection: {
|
||||
width: '70%',
|
||||
justifyContent: 'space-between',
|
||||
paddingLeft: 16,
|
||||
paddingRight: 16,
|
||||
},
|
||||
listItemSymbol: {
|
||||
paddingTop: 5,
|
||||
},
|
||||
listData: {
|
||||
justifyContent: 'space-between',
|
||||
flexDirection: 'row',
|
||||
width: '100%',
|
||||
},
|
||||
})
|
||||
|
||||
export default WalletPage
|
@ -20,6 +20,7 @@ import nostrosDarkTheme from './Constants/Theme/theme-dark.json'
|
||||
import { navigationRef } from './lib/Navigation'
|
||||
import { UserContextProvider } from './Contexts/UserContext'
|
||||
import NostrosDrawerNavigator from './Pages/NostrosDrawerNavigator'
|
||||
import { WalletContextProvider } from './Contexts/WalletContext'
|
||||
|
||||
export const Frontend: React.FC = () => {
|
||||
const { LightTheme, DarkTheme } = adaptNavigationTheme({
|
||||
@ -41,6 +42,7 @@ export const Frontend: React.FC = () => {
|
||||
<SafeAreaProvider>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<AppContextProvider>
|
||||
<WalletContextProvider>
|
||||
<UserContextProvider>
|
||||
<RelayPoolContextProvider>
|
||||
<React.Fragment>
|
||||
@ -50,6 +52,7 @@ export const Frontend: React.FC = () => {
|
||||
</React.Fragment>
|
||||
</RelayPoolContextProvider>
|
||||
</UserContextProvider>
|
||||
</WalletContextProvider>
|
||||
</AppContextProvider>
|
||||
</I18nextProvider>
|
||||
</SafeAreaProvider>
|
||||
|
Loading…
Reference in New Issue
Block a user