This commit is contained in:
KoalaSat 2023-03-19 14:21:52 +00:00 committed by GitHub
commit 223b1a374f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 703 additions and 70 deletions

View File

@ -237,6 +237,9 @@ public class Database {
try { try {
instance.execSQL("ALTER TABLE nostros_relays ADD COLUMN paid INT DEFAULT 0;"); instance.execSQL("ALTER TABLE nostros_relays ADD COLUMN paid INT DEFAULT 0;");
} catch (SQLException e) { } } 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 { public void saveEvent(JSONObject data, String userPubKey, String relayUrl) throws JSONException {

View File

@ -544,10 +544,12 @@ public class Event {
JSONArray eTags = filterTags("e"); JSONArray eTags = filterTags("e");
JSONArray bolt11Tags = filterTags("bolt11"); JSONArray bolt11Tags = filterTags("bolt11");
JSONArray descriptionTags = filterTags("description"); JSONArray descriptionTags = filterTags("description");
JSONArray preimageTags = filterTags("preimage");
String zapped_event_id = ""; String zapped_event_id = "";
String zapped_user_id = ""; String zapped_user_id = "";
String zapper_user_id = ""; String zapper_user_id = "";
String preimage = "";
double amount = 0; double amount = 0;
if (descriptionTags.length() > 0) { if (descriptionTags.length() > 0) {
JSONArray tag = descriptionTags.getJSONArray(0); JSONArray tag = descriptionTags.getJSONArray(0);
@ -564,6 +566,9 @@ public class Event {
if (pTags.length() > 0) { if (pTags.length() > 0) {
zapped_user_id = pTags.getJSONArray(pTags.length() - 1).getString(1); 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 = ?"; 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}); @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_user_id", zapped_user_id);
values.put("zapped_event_id", zapped_event_id); values.put("zapped_event_id", zapped_event_id);
values.put("zapper_user_id", zapper_user_id); values.put("zapper_user_id", zapper_user_id);
values.put("preimage", preimage);
database.insert("nostros_zaps", null, values); database.insert("nostros_zaps", null, values);
} }

View File

@ -7,6 +7,8 @@ import RBSheet from 'react-native-raw-bottom-sheet'
import { Card, IconButton, Text, useTheme } from 'react-native-paper' import { Card, IconButton, Text, useTheme } from 'react-native-paper'
import { AppContext } from '../../Contexts/AppContext' import { AppContext } from '../../Contexts/AppContext'
import { decode, type PaymentRequestObject, type TagsObject } from 'bolt11' import { decode, type PaymentRequestObject, type TagsObject } from 'bolt11'
import { WalletContext } from '../../Contexts/WalletContext'
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
interface LnPreviewProps { interface LnPreviewProps {
setOpen?: (open: boolean) => void setOpen?: (open: boolean) => void
@ -21,11 +23,13 @@ export const LnPreview: React.FC<LnPreviewProps> = ({
}) => { }) => {
const theme = useTheme() const theme = useTheme()
const { t } = useTranslation('common') const { t } = useTranslation('common')
const { active, payInvoice } = React.useContext(WalletContext)
const { getSatoshiSymbol } = React.useContext(AppContext) const { getSatoshiSymbol } = React.useContext(AppContext)
const bottomSheetInvoiceRef = React.useRef<RBSheet>(null) const bottomSheetInvoiceRef = React.useRef<RBSheet>(null)
const [decodedLnUrl, setDecodedLnUrl] = useState< const [decodedLnUrl, setDecodedLnUrl] = useState<
PaymentRequestObject & { tagsObject: TagsObject } PaymentRequestObject & { tagsObject: TagsObject }
>() >()
const [paymentDone, setPaymentDone] = useState<boolean>()
useEffect(() => { useEffect(() => {
if (invoice) { if (invoice) {
@ -43,6 +47,12 @@ export const LnPreview: React.FC<LnPreviewProps> = ({
Clipboard.setString(invoice ?? '') Clipboard.setString(invoice ?? '')
} }
const payWithWallet: () => void = () => {
if (invoice) {
payInvoice(invoice).then(setPaymentDone)
}
}
const openApp: () => void = () => { const openApp: () => void = () => {
Linking.openURL(`lightning:${invoice}`) Linking.openURL(`lightning:${invoice}`)
} }
@ -76,7 +86,15 @@ export const LnPreview: React.FC<LnPreviewProps> = ({
<Card style={styles.qrContainer}> <Card style={styles.qrContainer}>
<Card.Content> <Card.Content>
<View style={styles.qr}> <View style={styles.qr}>
<QRCode value={invoice} quietZone={8} size={Dimensions.get('window').width - 64} /> {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>
<View style={styles.qrText}> <View style={styles.qrText}>
<Text>{decodedLnUrl?.satoshis} </Text> <Text>{decodedLnUrl?.satoshis} </Text>
@ -89,8 +107,14 @@ export const LnPreview: React.FC<LnPreviewProps> = ({
<IconButton icon='content-copy' size={28} onPress={copyInvoice} /> <IconButton icon='content-copy' size={28} onPress={copyInvoice} />
<Text>{t('lnPayment.copy')}</Text> <Text>{t('lnPayment.copy')}</Text>
</View> </View>
{active && (
<View style={styles.actionButton}>
<IconButton icon='wallet' size={28} onPress={payWithWallet} />
<Text>{t('lnPayment.pay')}</Text>
</View>
)}
<View style={styles.actionButton}> <View style={styles.actionButton}>
<IconButton icon='wallet' size={28} onPress={openApp} /> <IconButton icon='exit-to-app' size={28} onPress={openApp} />
<Text>{t('lnPayment.open')}</Text> <Text>{t('lnPayment.open')}</Text>
</View> </View>
</View> </View>

View File

@ -18,10 +18,14 @@ import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityI
import { navigate } from '../../lib/Navigation' import { navigate } from '../../lib/Navigation'
import { usernamePubKey } from '../../Functions/RelayFunctions/Users' import { usernamePubKey } from '../../Functions/RelayFunctions/Users'
import ProfileData from '../ProfileData' import ProfileData from '../ProfileData'
import { WalletContext } from '../../Contexts/WalletContext'
import { AppContext } from '../../Contexts/AppContext'
export const MenuItems: React.FC = () => { export const MenuItems: React.FC = () => {
const [drawerItemIndex, setDrawerItemIndex] = React.useState<number>(-1) const [drawerItemIndex, setDrawerItemIndex] = React.useState<number>(-1)
const { getSatoshiSymbol } = React.useContext(AppContext)
const { relays } = React.useContext(RelayPoolContext) const { relays } = React.useContext(RelayPoolContext)
const { balance, active } = React.useContext(WalletContext)
const { const {
nPub, nPub,
publicKey, publicKey,
@ -61,6 +65,8 @@ export const MenuItems: React.FC = () => {
navigate('Config') navigate('Config')
} else if (key === 'contacts') { } else if (key === 'contacts') {
navigate('Contacts') navigate('Contacts')
} else if (key === 'wallet') {
navigate('Wallet')
} }
} }
@ -114,49 +120,65 @@ export const MenuItems: React.FC = () => {
)} )}
<Drawer.Section> <Drawer.Section>
{publicKey && ( {publicKey && (
<> <Drawer.Item
<Drawer.Item label={t('menuItems.relays')}
label={t('menuItems.relays')} icon={() => (
icon={() => ( <MaterialCommunityIcons
<MaterialCommunityIcons name='chart-timeline-variant'
name='chart-timeline-variant' size={25}
size={25} color={theme.colors.onPrimaryContainer}
color={theme.colors.onPrimaryContainer} />
/> )}
)} key='relays'
key='relays' active={drawerItemIndex === 0}
active={drawerItemIndex === 0} onPress={() => onPressItem('relays', 0)}
onPress={() => onPressItem('relays', 0)} onTouchEnd={() => setDrawerItemIndex(-1)}
onTouchEnd={() => setDrawerItemIndex(-1)} right={() =>
right={() => activerelays < 1 ? (
activerelays < 1 ? ( <Text style={{ color: theme.colors.error }}>{t('menuItems.notConnected')}</Text>
<Text style={{ color: theme.colors.error }}>{t('menuItems.notConnected')}</Text> ) : (
) : ( <Text style={{ color: '#7ADC70' }}>
<Text style={{ color: '#7ADC70' }}> {t('menuItems.connectedRelays', {
{t('menuItems.connectedRelays', { number: activerelays,
number: activerelays, })}
})} </Text>
</Text> )
) }
} />
/> )}
<Drawer.Item
<Drawer.Item label={t('menuItems.wallet')}
label={t('menuItems.contacts')} icon='wallet-outline'
icon='contacts-outline' key='wallet'
key='contacts' active={drawerItemIndex === 1}
active={drawerItemIndex === 1} onPress={() => onPressItem('wallet', 1)}
onPress={() => onPressItem('contacts', 1)} onTouchEnd={() => setDrawerItemIndex(-1)}
onTouchEnd={() => setDrawerItemIndex(-1)} right={() => {
/> if (!active) return <></>
</> return (
<Text>
{`${balance} `}
{getSatoshiSymbol()}
</Text>
)
}}
/>
{publicKey && (
<Drawer.Item
label={t('menuItems.contacts')}
icon='contacts-outline'
key='contacts'
active={drawerItemIndex === 1}
onPress={() => onPressItem('contacts', 1)}
onTouchEnd={() => setDrawerItemIndex(-1)}
/>
)} )}
<Drawer.Item <Drawer.Item
label={t('menuItems.configuration')} label={t('menuItems.configuration')}
icon='cog' icon='cog'
key='configuration' key='configuration'
active={drawerItemIndex === 1} active={drawerItemIndex === 2}
onPress={() => onPressItem('config', 1)} onPress={() => onPressItem('config', 2)}
onTouchEnd={() => setDrawerItemIndex(-1)} onTouchEnd={() => setDrawerItemIndex(-1)}
/> />
</Drawer.Section> </Drawer.Section>
@ -165,16 +187,16 @@ export const MenuItems: React.FC = () => {
label={t('menuItems.about')} label={t('menuItems.about')}
icon='information-outline' icon='information-outline'
key='about' key='about'
active={drawerItemIndex === 2} active={drawerItemIndex === 3}
onPress={() => onPressItem('about', 2)} onPress={() => onPressItem('about', 3)}
onTouchEnd={() => setDrawerItemIndex(-1)} onTouchEnd={() => setDrawerItemIndex(-1)}
/> />
<Drawer.Item <Drawer.Item
label={t('menuItems.faq')} label={t('menuItems.faq')}
icon='comment-question-outline' icon='comment-question-outline'
key='faq' key='faq'
active={drawerItemIndex === 2} active={drawerItemIndex === 4}
onPress={() => onPressItem('faq', 2)} onPress={() => onPressItem('faq', 4)}
onTouchEnd={() => setDrawerItemIndex(-1)} onTouchEnd={() => setDrawerItemIndex(-1)}
/> />
<Drawer.Item <Drawer.Item

View File

@ -13,7 +13,7 @@ interface ProfileCardProps {
publicKey?: string publicKey?: string
lnurl?: string lnurl?: string
lnAddress?: string lnAddress?: string
validNip05?: number | undefined validNip05?: number | boolean | undefined
nip05?: string nip05?: string
picture?: string picture?: string
avatarSize?: number avatarSize?: number

View 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)

View File

@ -14,6 +14,7 @@ export interface Zap extends Event {
nip05: string nip05: string
lnurl: string lnurl: string
ln_address: string ln_address: string
preimage: string
} }
const databaseToEntity: (object: any) => Zap = (object) => { const databaseToEntity: (object: any) => Zap = (object) => {
@ -109,7 +110,7 @@ export const getUserZaps: (
export const getZaps: ( export const getZaps: (
db: QuickSQLiteConnection, db: QuickSQLiteConnection,
filters: { eventId?: string; zapperId?: string; limit?: number }, filters: { eventId?: string; zapperId?: string; limit?: number; preimages?: string[] },
) => Promise<Zap[]> = async (db, filters) => { ) => Promise<Zap[]> = async (db, filters) => {
let groupsQuery = ` let groupsQuery = `
SELECT SELECT
@ -121,6 +122,12 @@ export const getZaps: (
nostros_users ON nostros_users.id = nostros_zaps.zapper_user_id nostros_users ON nostros_users.id = nostros_zaps.zapper_user_id
` `
if (filters.preimages) {
groupsQuery += `
WHERE preimage IN ("${filters.preimages.join('", "')}")
`
}
if (filters.eventId) { if (filters.eventId) {
groupsQuery += ` groupsQuery += `
WHERE zapped_event_id = "${filters.eventId}" WHERE zapped_event_id = "${filters.eventId}"

View File

@ -2,9 +2,10 @@
"common": { "common": {
"time": { "time": {
"today": "Today", "today": "Today",
"yerterday": "Yesterday" "yesterday": "Yesterday"
}, },
"drawers": { "drawers": {
"walletLogout": "Logout",
"relaysTitle": "Relays", "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.", "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?", "keysTitle": "Was sind Schlüssel?",
@ -28,6 +29,7 @@
"homeNavigator": { "homeNavigator": {
"ProfileCreate": "Profil anlegen", "ProfileCreate": "Profil anlegen",
"Search": "", "Search": "",
"Wallet": "Wallet",
"ImageGallery": "", "ImageGallery": "",
"ProfileConnect": "", "ProfileConnect": "",
"Group": "", "Group": "",
@ -172,7 +174,8 @@
"copy": "Kopieren", "copy": "Kopieren",
"open": "Öffne Wallet", "open": "Öffne Wallet",
"anonTip": "Anonymer Tip", "anonTip": "Anonymer Tip",
"zap": "Zap" "zap": "Zap",
"pay": "Pay"
}, },
"notificationsFeed": { "notificationsFeed": {
"connectContactRelays": "Reconnect to contacts' relays", "connectContactRelays": "Reconnect to contacts' relays",

View File

@ -2,9 +2,10 @@
"common": { "common": {
"time": { "time": {
"today": "Today", "today": "Today",
"yerterday": "Yesterday" "yesterday": "Yesterday"
}, },
"drawers": { "drawers": {
"walletLogout": "Logout",
"relaysTitle": "Relays", "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.", "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?", "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.", "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" "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": { "homeNavigator": {
"ProfileCreate": "Create profile", "ProfileCreate": "Create profile",
"Search": "", "Search": "",
"Wallet": "Wallet",
"ImageGallery": "", "ImageGallery": "",
"ProfileConnect": "", "ProfileConnect": "",
"Group": "", "Group": "",
@ -106,6 +113,7 @@
"poweredBy": "Powered by {{uri}}" "poweredBy": "Powered by {{uri}}"
}, },
"menuItems": { "menuItems": {
"wallet": "Wallet",
"relays": "Relays", "relays": "Relays",
"notConnected": "Not connected", "notConnected": "Not connected",
"connectedRelays": "{{number}} connected", "connectedRelays": "{{number}} connected",
@ -175,7 +183,8 @@
"copy": "Copy", "copy": "Copy",
"open": "Open wallet", "open": "Open wallet",
"anonTip": "Anonymous tip", "anonTip": "Anonymous tip",
"zap": "Zap" "zap": "Zap",
"pay": "Pay"
}, },
"notificationsFeed": { "notificationsFeed": {
"reposted": "Reposted", "reposted": "Reposted",

View File

@ -2,9 +2,10 @@
"common": { "common": {
"time": { "time": {
"today": "Hoy", "today": "Hoy",
"yerterday": "Ayer" "yesterday": "Ayer"
}, },
"drawers": { "drawers": {
"walletLogout": "Logout",
"relaysTitle": "Relés", "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.", "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?", "keysTitle": "¿Qué son las claves?",
@ -39,6 +40,7 @@
"Group": "", "Group": "",
"QrReader": "", "QrReader": "",
"Search": "", "Search": "",
"Wallet": "Wallet",
"ImageGallery": "", "ImageGallery": "",
"ProfileCreate": "Crear perfil", "ProfileCreate": "Crear perfil",
"ProfileConnect": "", "ProfileConnect": "",
@ -188,7 +190,8 @@
"copy": "Copiar", "copy": "Copiar",
"open": "Abrir wallet", "open": "Abrir wallet",
"anonTip": "Propina anónima", "anonTip": "Propina anónima",
"zap": "Zap" "zap": "Zap",
"pay": "Pagar"
}, },
"notificationsFeed": { "notificationsFeed": {
"reposted": "Reposteado", "reposted": "Reposteado",

View File

@ -2,9 +2,10 @@
"common": { "common": {
"time": { "time": {
"today": "Today", "today": "Today",
"yerterday": "Yesterday" "yesterday": "Yesterday"
}, },
"drawers": { "drawers": {
"walletLogout": "Logout",
"relaysTitle": "Relais", "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.", "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 ?", "keysTitle": "C'est quoi les clés ?",
@ -39,6 +40,7 @@
"Group": "", "Group": "",
"QrReader": "", "QrReader": "",
"ImageGallery": "", "ImageGallery": "",
"Wallet": "Wallet",
"Search": "", "Search": "",
"ProfileCreate": "Create profile", "ProfileCreate": "Create profile",
"ProfileConnect": "", "ProfileConnect": "",
@ -193,7 +195,8 @@
"copy": "Copier", "copy": "Copier",
"open": "Ouvrir le wallet", "open": "Ouvrir le wallet",
"anonTip": "Anonymous tip", "anonTip": "Anonymous tip",
"zap": "Zap" "zap": "Zap",
"pay": "Pay"
}, },
"notificationsFeed": { "notificationsFeed": {
"reposted": "Reposted", "reposted": "Reposted",

View File

@ -2,9 +2,10 @@
"common": { "common": {
"time": { "time": {
"today": "Today", "today": "Today",
"yerterday": "Yesterday" "yesterday": "Yesterday"
}, },
"drawers": { "drawers": {
"walletLogout": "Logout",
"relaysTitle": "Реле", "relaysTitle": "Реле",
"relaysDescription": "Реле(ретрансляторы) — это сетевые узлы, которые работают как посредники для передачи сообщений между приложениями.\n\n\nРетрансляторы можно использовать для повышения отказоустойчивости и доступности сети, позволяя доставлять сообщения даже при сбоях и проблемах с подключением. n\n\nРеле также можно использовать для повышения конфиденциальности и сетевой безопасности, поскольку они могут скрывать местоположение и идентификационные данные приложений, которые обмениваются данными. Это может быть полезно в условиях цензуры или слежки.\n\n \nВажно отметить, что реле также могут использоваться в злонамеренных целях, таких как прослушивание или цензура сетевого трафика.\n \n\nПоэтому важно тщательно оценивать использование реле и принимать соответствующие меры безопасности для защиты конфиденциальности и сетевой безопасности.", "relaysDescription": "Реле(ретрансляторы) — это сетевые узлы, которые работают как посредники для передачи сообщений между приложениями.\n\n\nРетрансляторы можно использовать для повышения отказоустойчивости и доступности сети, позволяя доставлять сообщения даже при сбоях и проблемах с подключением. n\n\nРеле также можно использовать для повышения конфиденциальности и сетевой безопасности, поскольку они могут скрывать местоположение и идентификационные данные приложений, которые обмениваются данными. Это может быть полезно в условиях цензуры или слежки.\n\n \nВажно отметить, что реле также могут использоваться в злонамеренных целях, таких как прослушивание или цензура сетевого трафика.\n \n\nПоэтому важно тщательно оценивать использование реле и принимать соответствующие меры безопасности для защиты конфиденциальности и сетевой безопасности.",
"keysTitle": "Что такое ключи?", "keysTitle": "Что такое ключи?",
@ -38,6 +39,7 @@
"homeNavigator": { "homeNavigator": {
"Group": "", "Group": "",
"QrReader": "", "QrReader": "",
"Wallet": "Wallet",
"ImageGallery": "", "ImageGallery": "",
"Search": "", "Search": "",
"ProfileCreate": "Create profile", "ProfileCreate": "Create profile",
@ -189,7 +191,8 @@
"copy": "Скопировать", "copy": "Скопировать",
"open": "Открыть кошелек", "open": "Открыть кошелек",
"anonTip": "Anonymous tip", "anonTip": "Anonymous tip",
"zap": "Zap" "zap": "Zap",
"pay": "Pay"
}, },
"notificationsFeed": { "notificationsFeed": {
"reposted": "Reposted", "reposted": "Reposted",

View File

@ -2,9 +2,10 @@
"common": { "common": {
"time": { "time": {
"today": "Today", "today": "Today",
"yerterday": "Yesterday" "yesterday": "Yesterday"
}, },
"drawers": { "drawers": {
"walletLogout": "Logout",
"relaysTitle": "关于中继", "relaysTitle": "关于中继",
"relaysDescription": "中继是网络上的节点,作为应用程序之间传输消息的中介。\n\n\n中继可用于提高网络的弹性和可用性即使在连接出现故障或中断的情况下也能传递消息。 \n\n\n中继还可用于提高隐私和网络安全因为它们可以隐藏相互通信的用户的位置和身份。 \n\n\n这在审查或监视是一个问题的环境中是很有用的。 \n\n\n需要注意的是中继也可以用于恶意的目的如嗅探或审查网络流量。", "relaysDescription": "中继是网络上的节点,作为应用程序之间传输消息的中介。\n\n\n中继可用于提高网络的弹性和可用性即使在连接出现故障或中断的情况下也能传递消息。 \n\n\n中继还可用于提高隐私和网络安全因为它们可以隐藏相互通信的用户的位置和身份。 \n\n\n这在审查或监视是一个问题的环境中是很有用的。 \n\n\n需要注意的是中继也可以用于恶意的目的如嗅探或审查网络流量。",
"keysTitle": "这些密钥是什么?", "keysTitle": "这些密钥是什么?",
@ -40,6 +41,7 @@
"QrReader": "", "QrReader": "",
"ImageGallery": "", "ImageGallery": "",
"Search": "", "Search": "",
"Wallet": "Wallet",
"ProfileCreate": "创建用户", "ProfileCreate": "创建用户",
"ProfileConnect": "", "ProfileConnect": "",
"Contacts": "联系人", "Contacts": "联系人",
@ -187,7 +189,8 @@
"copy": "复制", "copy": "复制",
"open": "打开钱包", "open": "打开钱包",
"anonTip": "匿名赞赏", "anonTip": "匿名赞赏",
"zap": "赞赏" "zap": "赞赏",
"pay": "Pay"
}, },
"notificationsFeed": { "notificationsFeed": {
"reposted": "Reposted", "reposted": "Reposted",

View File

@ -2,7 +2,7 @@ import * as React from 'react'
import { Platform, View } from 'react-native' import { Platform, View } from 'react-native'
import type { DrawerNavigationProp } from '@react-navigation/drawer' import type { DrawerNavigationProp } from '@react-navigation/drawer'
import { CardStyleInterpolators, createStackNavigator } from '@react-navigation/stack' 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 RBSheet from 'react-native-raw-bottom-sheet'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import HomePage from '../HomePage' import HomePage from '../HomePage'
@ -29,11 +29,14 @@ import DatabaseModule from '../../lib/Native/DatabaseModule'
import ImageGalleryPage from '../ImageGalleryPage' import ImageGalleryPage from '../ImageGalleryPage'
import { navigate } from '../../lib/Navigation' import { navigate } from '../../lib/Navigation'
import SearchPage from '../SearchPage' import SearchPage from '../SearchPage'
import WalletPage from '../WalletPage'
import { WalletContext } from '../../Contexts/WalletContext'
export const HomeNavigator: React.FC = () => { export const HomeNavigator: React.FC = () => {
const theme = useTheme() const theme = useTheme()
const { t } = useTranslation('common') const { t } = useTranslation('common')
const { displayRelayDrawer, setDisplayrelayDrawer } = React.useContext(RelayPoolContext) const { displayRelayDrawer, setDisplayrelayDrawer } = React.useContext(RelayPoolContext)
const { logoutWallet } = React.useContext(WalletContext)
const { const {
displayUserDrawer, displayUserDrawer,
setDisplayNoteDrawer, setDisplayNoteDrawer,
@ -46,6 +49,7 @@ export const HomeNavigator: React.FC = () => {
const bottomSheetProfileRef = React.useRef<RBSheet>(null) const bottomSheetProfileRef = React.useRef<RBSheet>(null)
const bottomSheetNoteRef = React.useRef<RBSheet>(null) const bottomSheetNoteRef = React.useRef<RBSheet>(null)
const bottomSheetRelayRef = React.useRef<RBSheet>(null) const bottomSheetRelayRef = React.useRef<RBSheet>(null)
const bottomWalletRef = React.useRef<RBSheet>(null)
const Stack = React.useMemo(() => createStackNavigator(), []) const Stack = React.useMemo(() => createStackNavigator(), [])
const cardStyleInterpolator = React.useMemo( const cardStyleInterpolator = React.useMemo(
() => () =>
@ -131,6 +135,12 @@ export const HomeNavigator: React.FC = () => {
route.params?.title ? route.params?.title : t(`homeNavigator.${route.name}`) 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) && ( {['Profile', 'Conversation'].includes(route.name) && (
<Appbar.Action <Appbar.Action
icon='dots-vertical' icon='dots-vertical'
@ -187,6 +197,7 @@ export const HomeNavigator: React.FC = () => {
<Stack.Screen name='Search' component={SearchPage} /> <Stack.Screen name='Search' component={SearchPage} />
</Stack.Group> </Stack.Group>
<Stack.Group> <Stack.Group>
<Stack.Screen name='Wallet' component={WalletPage} />
<Stack.Screen name='Contacts' component={ContactsPage} /> <Stack.Screen name='Contacts' component={ContactsPage} />
<Stack.Screen name='Relays' component={RelaysPage} /> <Stack.Screen name='Relays' component={RelaysPage} />
<Stack.Screen name='About' component={AboutPage} /> <Stack.Screen name='About' component={AboutPage} />
@ -228,6 +239,19 @@ export const HomeNavigator: React.FC = () => {
<Text variant='bodyMedium'>{t('drawers.relaysDescription')}</Text> <Text variant='bodyMedium'>{t('drawers.relaysDescription')}</Text>
</View> </View>
</RBSheet> </RBSheet>
<RBSheet ref={bottomWalletRef} closeOnDragDown={true} customStyles={bottomSheetStyles}>
<View>
<Button
mode='contained'
onPress={() => {
logoutWallet()
bottomWalletRef.current?.close()
}}
>
{t('drawers.walletLogout')}
</Button>
</View>
</RBSheet>
</> </>
) )
} }

View File

@ -38,7 +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 { handleInfinityScroll } from '../../../Functions/NativeFunctions' import { formatHour, handleInfinityScroll } from '../../../Functions/NativeFunctions'
export const ConversationsFeed: React.FC = () => { export const ConversationsFeed: React.FC = () => {
const initialPageSize = 14 const initialPageSize = 14
@ -154,7 +154,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>{formatHour(item?.created_at, false)}</Text> <Text>{formatHour(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>

View File

@ -130,6 +130,7 @@ export const ThirdStep: React.FC<ThirdStepProps> = ({ nextStep, skip }) => {
data={asignation} data={asignation}
renderItem={renderItem} renderItem={renderItem}
ItemSeparatorComponent={Divider} ItemSeparatorComponent={Divider}
style={styles.list}
/> />
</Card.Content> </Card.Content>
</Card> </Card>
@ -182,6 +183,9 @@ const styles = StyleSheet.create({
relayColor: { relayColor: {
paddingTop: 9, paddingTop: 9,
}, },
list: {
maxHeight: 230,
},
}) })
export default ThirdStep export default ThirdStep

View 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

View File

@ -20,6 +20,7 @@ import nostrosDarkTheme from './Constants/Theme/theme-dark.json'
import { navigationRef } from './lib/Navigation' import { navigationRef } from './lib/Navigation'
import { UserContextProvider } from './Contexts/UserContext' import { UserContextProvider } from './Contexts/UserContext'
import NostrosDrawerNavigator from './Pages/NostrosDrawerNavigator' import NostrosDrawerNavigator from './Pages/NostrosDrawerNavigator'
import { WalletContextProvider } from './Contexts/WalletContext'
export const Frontend: React.FC = () => { export const Frontend: React.FC = () => {
const { LightTheme, DarkTheme } = adaptNavigationTheme({ const { LightTheme, DarkTheme } = adaptNavigationTheme({
@ -41,15 +42,17 @@ export const Frontend: React.FC = () => {
<SafeAreaProvider> <SafeAreaProvider>
<I18nextProvider i18n={i18n}> <I18nextProvider i18n={i18n}>
<AppContextProvider> <AppContextProvider>
<UserContextProvider> <WalletContextProvider>
<RelayPoolContextProvider> <UserContextProvider>
<React.Fragment> <RelayPoolContextProvider>
<SafeAreaInsetsContext.Consumer> <React.Fragment>
{() => <NostrosDrawerNavigator />} <SafeAreaInsetsContext.Consumer>
</SafeAreaInsetsContext.Consumer> {() => <NostrosDrawerNavigator />}
</React.Fragment> </SafeAreaInsetsContext.Consumer>
</RelayPoolContextProvider> </React.Fragment>
</UserContextProvider> </RelayPoolContextProvider>
</UserContextProvider>
</WalletContextProvider>
</AppContextProvider> </AppContextProvider>
</I18nextProvider> </I18nextProvider>
</SafeAreaProvider> </SafeAreaProvider>