This commit is contained in:
KoalaSat 2023-02-19 18:42:43 +00:00 committed by GitHub
commit c9c3eae666
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 670 additions and 247 deletions

View File

@ -247,6 +247,9 @@ android {
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
// // https://stackoverflow.com/questions/56639529/duplicate-class-com-google-common-util-concurrent-listenablefuture-found-in-modu
// implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+" // From node_modules
@ -258,6 +261,8 @@ dependencies {
implementation 'com.facebook.fresco:webpsupport:2.6.0'
implementation 'com.facebook.fresco:animated-gif:2.6.0'
// implementation 'com.github.Samourai-Wallet:ExtLibJ:0.0.11'
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
exclude group:'com.facebook.fbjni'

View File

@ -9,6 +9,8 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
//import com.samourai.wallet.segwit.bech32.Bech32;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
@ -74,6 +76,8 @@ public class Event {
muteUser(database);
} else if (kind.equals("1002")) {
saveRelays(database);
} else if (kind.equals("9735")) {
saveZap(database);
}
} catch (JSONException e) {
e.printStackTrace();
@ -195,6 +199,45 @@ public class Event {
return false;
}
protected String getZapPubkey(String lnurl, String ln_address) {
String pointer = ln_address;
if (pointer.isEmpty() || pointer.equals("")) {
pointer = lnurl;
}
if (pointer.isEmpty() || pointer.equals("")) {
return "";
}
String[] parts = pointer.split("@");
if (parts.length == 2) {
String name = parts[0];
String domain = parts[1];
if (!name.matches("^[a-z0-9-_]+$")) return "";
try {
String url = "https://" + domain + "/.well-known/lnurlp/" + name;
JSONObject response = getJSONObjectFromURL(url);
Boolean allowsNostr = response.getBoolean("allowsNostr");
if (allowsNostr) {
String nostrPubkey = response.getString("nostrPubkey");
return nostrPubkey;
}
} catch (IOException | JSONException e) {
e.printStackTrace();
}
} else {
// try {
// Pair<String, byte[]> words = Bech32.bech32Decode(pointer);
// } catch (Exception e) {
// e.printStackTrace();
// }
}
return "";
}
protected void saveNote(SQLiteDatabase database, String userPubKey) {
ContentValues values = new ContentValues();
values.put("id", id);
@ -361,66 +404,127 @@ public class Event {
}
protected void saveReaction(SQLiteDatabase database) throws JSONException {
JSONArray pTags = filterTags("p");
JSONArray eTags = filterTags("e");
String query = "SELECT created_at FROM nostros_reactions WHERE id = ?";
@SuppressLint("Recycle") Cursor cursor = database.rawQuery(query, new String[] {id});
String reacted_event_id = "";
String reacted_user_id = "";
if (eTags.length() > 0) {
reacted_event_id = eTags.getJSONArray(eTags.length() - 1).getString(1);
if (cursor.getCount() == 0) {
JSONArray pTags = filterTags("p");
JSONArray eTags = filterTags("e");
String reacted_event_id = "";
String reacted_user_id = "";
if (eTags.length() > 0) {
reacted_event_id = eTags.getJSONArray(eTags.length() - 1).getString(1);
}
if (pTags.length() > 0) {
reacted_user_id = pTags.getJSONArray(pTags.length() - 1).getString(1);
}
ContentValues values = new ContentValues();
values.put("id", id);
values.put("content", content);
values.put("created_at", created_at);
values.put("kind", kind);
values.put("pubkey", pubkey);
values.put("sig", sig);
values.put("tags", tags.toString());
values.put("positive", !content.equals("-"));
values.put("reacted_event_id", reacted_event_id);
values.put("reacted_user_id", reacted_user_id);
database.insert("nostros_reactions", null, values);
}
if (pTags.length() > 0) {
reacted_user_id = pTags.getJSONArray(pTags.length() - 1).getString(1);
}
ContentValues values = new ContentValues();
values.put("id", id);
values.put("content", content);
values.put("created_at", created_at);
values.put("kind", kind);
values.put("pubkey", pubkey);
values.put("sig", sig);
values.put("tags", tags.toString());
values.put("positive", !content.equals("-"));
values.put("reacted_event_id", reacted_event_id);
values.put("reacted_user_id", reacted_user_id);
database.insert("nostros_reactions", null, values);
}
protected void saveUserMeta(SQLiteDatabase database) throws JSONException {
JSONObject userContent = new JSONObject(content);
String query = "SELECT created_at, valid_nip05, nip05 FROM nostros_users WHERE id = ?";
String query = "SELECT created_at, valid_nip05, nip05, zap_pubkey FROM nostros_users WHERE id = ?";
@SuppressLint("Recycle") Cursor cursor = database.rawQuery(query, new String[] {pubkey});
String nip05 = userContent.optString("nip05");
String lud = userContent.optString("lud06");
if (lud.isEmpty()) {
lud = userContent.optString("lud16");
}
String lnurl = userContent.optString("lud06");
String ln_address = userContent.optString("lud16");
ContentValues values = new ContentValues();
values.put("name", userContent.optString("name"));
values.put("picture", userContent.optString("picture"));
values.put("about", userContent.optString("about"));
values.put("lnurl", lud);
values.put("lnurl", lnurl);
values.put("ln_address", ln_address);
values.put("nip05", nip05);
values.put("main_relay", userContent.optString("main_relay"));
values.put("created_at", created_at);
if (cursor.getCount() == 0) {
values.put("valid_nip05", validateNip05(nip05) ? 1 : 0);
values.put("zap_pubkey", getZapPubkey(lnurl, ln_address));
values.put("id", pubkey);
values.put("valid_nip05", validateNip05(nip05) ? 1 : 0);
values.put("blocked", 0);
database.insert("nostros_users", null, values);
} else if (cursor.moveToFirst() && created_at > cursor.getInt(0)) {
if (cursor.getInt(1) == 0 || !cursor.getString(2).equals(nip05)) {
values.put("valid_nip05", validateNip05(nip05) ? 1 : 0);
}
} else if (cursor.moveToFirst()){
String whereClause = "id = ?";
String[] whereArgs = new String[] {
String[] whereArgs = new String[]{
this.pubkey
};
database.update("nostros_users", values, whereClause, whereArgs);
if (created_at >= cursor.getInt(0) ||
cursor.isNull(1) ||
cursor.getInt(1) == 0) {
values.put("valid_nip05", validateNip05(nip05) ? 1 : 0);
database.update("nostros_users", values, whereClause, whereArgs);
}
if (created_at >= cursor.getInt(0) ||
cursor.isNull(3) ||
cursor.getString(3).equals("")) {
values.put("zap_pubkey", getZapPubkey(lnurl, ln_address));
database.update("nostros_users", values, whereClause, whereArgs);
}
}
}
protected void saveZap(SQLiteDatabase database) throws JSONException {
String query = "SELECT created_at FROM nostros_zaps WHERE id = ?";
@SuppressLint("Recycle") Cursor cursor = database.rawQuery(query, new String[] {id});
if (cursor.getCount() == 0) {
JSONArray pTags = filterTags("p");
JSONArray eTags = filterTags("e");
JSONArray bolt11Tags = filterTags("bolt11");
JSONArray descriptionTags = filterTags("description");
String zapped_event_id = "";
String zapped_user_id = "";
String zapper_user_id = "";
double amount = 0;
if (descriptionTags.length() > 0) {
JSONArray tag = descriptionTags.getJSONArray(0);
JSONObject description = new JSONObject(tag.getString(1));
zapper_user_id = description.getString("pubkey");
}
if (bolt11Tags.length() > 0) {
String lnbc = bolt11Tags.getJSONArray(0).getString(1);
amount = getLnAmount(lnbc);
}
if (eTags.length() > 0) {
zapped_event_id = eTags.getJSONArray(eTags.length() - 1).getString(1);
}
if (pTags.length() > 0) {
zapped_user_id = pTags.getJSONArray(pTags.length() - 1).getString(1);
}
ContentValues values = new ContentValues();
values.put("id", id);
values.put("content", content);
values.put("created_at", created_at);
values.put("kind", kind);
values.put("pubkey", pubkey);
values.put("sig", sig);
values.put("tags", tags.toString());
values.put("amount", amount);
values.put("zapped_user_id", zapped_user_id);
values.put("zapped_event_id", zapped_event_id);
values.put("zapper_user_id", zapper_user_id);
database.insert("nostros_zaps", null, values);
}
}
@ -554,4 +658,24 @@ public class Event {
return new JSONObject(jsonString);
}
protected static double getLnAmount(String lnbc) {
double amount = 0.0;
Matcher mili = Pattern.compile("^lnbc(\\d*)m\\S*$").matcher(lnbc);
Matcher micro = Pattern.compile("^lnbc(\\d*)u\\S*$").matcher(lnbc);
Matcher nano = Pattern.compile("^lnbc(\\d*)n\\S*$").matcher(lnbc);
Matcher pico = Pattern.compile("^lnbc(\\d*)p\\S*$").matcher(lnbc);
if (mili.find()) {
amount = 100000 * Integer.parseInt(mili.group(1));
} else if (micro.find()) {
amount = 100 * Integer.parseInt(micro.group(1));
} else if (nano.find()) {
amount = 0.1 * Integer.parseInt(nano.group(1));
} else if (pico.find()) {
amount = 0.0001 * Integer.parseInt(pico.group(1));
}
return amount;
}
}

View File

@ -174,9 +174,28 @@ public class DatabaseModule {
database.execSQL("ALTER TABLE nostros_group_messages ADD COLUMN user_mentioned INT DEFAULT 0;");
} catch (SQLException e) { }
try {
database.execSQL("ALTER TABLE updated_at ADD COLUMN mode INT DEFAULT 0;");
database.execSQL("ALTER TABLE nostros_relays ADD COLUMN updated_at INT DEFAULT 0;");
database.execSQL("ALTER TABLE nostros_relays ADD COLUMN mode TEXT;");
} catch (SQLException e) { }
try {
database.execSQL("ALTER TABLE nostros_users ADD COLUMN ln_address TEXT;");
database.execSQL("UPDATE nostros_users SET ln_address=lnurl;");
database.execSQL("ALTER TABLE nostros_users ADD COLUMN zap_pubkey TEXT;");
database.execSQL("CREATE TABLE IF NOT EXISTS nostros_zaps(\n" +
" id TEXT PRIMARY KEY NOT NULL, \n" +
" content TEXT NOT NULL,\n" +
" created_at INT NOT NULL,\n" +
" kind INT NOT NULL,\n" +
" pubkey TEXT NOT NULL,\n" +
" sig TEXT NOT NULL,\n" +
" tags TEXT NOT NULL,\n" +
" amount FLOAT NOT NULL,\n" +
" zapped_user_id TEXT NOT NULL,\n" +
" zapper_user_id TEXT NOT NULL,\n" +
" zapped_event_id TEXT\n" +
" );");
database.execSQL("CREATE INDEX nostros_nostros_zaps_zapped_event_id_index ON nostros_zaps(zapped_event_id);");
} catch (SQLException e) { }
}
public void saveEvent(JSONObject data, String userPubKey, String relayUrl) throws JSONException {

View File

@ -164,7 +164,8 @@ export const GroupHeaderIcon: React.FC<GroupHeaderIconProps> = ({ groupId }) =>
publicKey={user?.id}
validNip05={user?.valid_nip05}
nip05={user?.nip05}
lud06={user?.lnurl}
lnurl={user?.lnurl}
lnAddress={user?.ln_address}
picture={user?.picture}
/>
</View>

View File

@ -1,15 +1,26 @@
import React, { useEffect, useMemo, useState } from 'react'
import { requestInvoice } from 'lnurl-pay'
import { User } from '../../Functions/DatabaseFunctions/Users'
import { StyleSheet, View } from 'react-native'
import { ListRenderItem, StyleSheet, View } from 'react-native'
import { useTranslation } from 'react-i18next'
import RBSheet from 'react-native-raw-bottom-sheet'
import { Button, Text, TextInput, useTheme } from 'react-native-paper'
import { Button, Divider, Text, TextInput, TouchableRipple, useTheme } from 'react-native-paper'
import { AppContext } from '../../Contexts/AppContext'
import LnPreview from '../LnPreview'
import { Note } from '../../Functions/DatabaseFunctions/Notes'
import { getNpub } from '../../lib/nostr/Nip19'
import { formatPubKey } from '../../Functions/RelayFunctions/Users'
import { getZaps, Zap } from '../../Functions/DatabaseFunctions/Zaps'
import { FlatList, ScrollView } from 'react-native-gesture-handler'
import ProfileData from '../ProfileData'
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
import { Kind } from 'nostr-tools'
import { getUnixTime } from 'date-fns'
import { Event, signEvent } from '../../lib/nostr/Events'
import { getRelays, Relay } from '../../Functions/DatabaseFunctions/Relays'
import { UserContext } from '../../Contexts/UserContext'
import { requestInvoiceWithServiceParams, requestPayServiceParams } from 'lnurl-pay'
import axios from 'axios'
interface LnPaymentProps {
open: boolean
@ -21,18 +32,37 @@ interface LnPaymentProps {
export const LnPayment: React.FC<LnPaymentProps> = ({ open, setOpen, note, user }) => {
const theme = useTheme()
const { t } = useTranslation('common')
const { getSatoshiSymbol } = React.useContext(AppContext)
const { getSatoshiSymbol, database, setDisplayUserDrawer } = React.useContext(AppContext)
const { relayPool, lastEventId } = React.useContext(RelayPoolContext)
const { publicKey, privateKey } = React.useContext(UserContext)
const bottomSheetLnPaymentRef = React.useRef<RBSheet>(null)
const [monto, setMonto] = useState<string>('')
const defaultComment = note?.id ? `Nostr: ${formatPubKey(getNpub(note?.id))}` : ''
const [comment, setComment] = useState<string>(defaultComment)
const [invoice, setInvoice] = useState<string>()
const [loading, setLoading] = useState<boolean>(false)
const [isZap, setIsZap] = useState<boolean>(false)
const [zapsUpdated, setZapsUpdated] = useState<number>(0)
const [zaps, setZaps] = useState<Zap[]>([])
const lnurl = useMemo(() => user?.lnurl ?? note?.lnurl, [open])
const lnAddress = useMemo(() => user?.ln_address ?? note?.ln_address, [open])
const userId = user?.id ?? note?.pubkey
const zapPubkey = user?.zap_pubkey ?? note?.zap_pubkey
useEffect(() => {
setMonto('')
if (open) {
if (database && note?.id) {
getZaps(database, note?.id).then((results) => {
relayPool?.subscribe('zappers-meta', [
{
kinds: [Kind.Metadata],
authors: results.filter((zap) => !zap.name).map((zap) => zap.zapper_user_id),
},
])
setZaps(results)
})
}
bottomSheetLnPaymentRef.current?.open()
} else {
bottomSheetLnPaymentRef.current?.close()
@ -40,16 +70,65 @@ export const LnPayment: React.FC<LnPaymentProps> = ({ open, setOpen, note, user
}, [open])
useEffect(() => {
setComment(defaultComment)
}, [note, open])
if (database && note?.id) {
getZaps(database, note?.id).then((results) => {
setZaps(results)
setZapsUpdated(getUnixTime(new Date()))
})
}
}, [lastEventId])
const generateInvoice: () => void = async () => {
if (lnurl && monto !== '') {
useEffect(() => {
bottomSheetLnPaymentRef.current?.forceUpdate()
}, [zapsUpdated])
const generateInvoice: (zap: boolean) => void = async (zap) => {
setIsZap(zap)
const lud = lnAddress && lnAddress !== '' ? lnAddress : lnurl
if (lud && lud !== '' && monto !== '') {
setLoading(true)
requestInvoice({
lnUrlOrAddress: lnurl,
tokens: parseInt(monto, 10) ?? 0,
const tokens: number = parseInt(monto, 10) ?? 0
let nostr: string
if (zap && database && privateKey && publicKey && zapPubkey && userId) {
const relays: Relay[] = await getRelays(database)
const tags = [
['p', userId],
['amount', (tokens * 1000).toString()],
['relays', ...relays.map((relay) => relay.url)],
]
if (note?.id) tags.push(['e', note.id])
const event: Event = {
content: comment,
created_at: getUnixTime(new Date()),
kind: 9734,
pubkey: publicKey,
tags,
}
const signedEvent = await signEvent(event, privateKey)
nostr = JSON.stringify(signedEvent)
}
const serviceParams = await requestPayServiceParams({ lnUrlOrAddress: lud })
requestInvoiceWithServiceParams({
params: serviceParams,
lnUrlOrAddress: lud,
tokens,
comment,
fetchGet: async ({ url, params }) => {
if (params && nostr && serviceParams.rawData.allowsNostr) {
params.nostr = nostr
}
const response = await axios.get(url, {
params,
})
console.log(response)
return response.data
},
})
.then((action) => {
if (action.hasValidAmount && action.invoice) {
@ -63,6 +142,37 @@ export const LnPayment: React.FC<LnPaymentProps> = ({ open, setOpen, note, user
}
}
const renderZapperItem: ListRenderItem<Zap> = ({ item }) => {
return (
<TouchableRipple onPress={() => setDisplayUserDrawer(item.user_id)}>
<View key={item.id} style={styles.zapperRow}>
<View style={styles.zapperData}>
<ProfileData
username={item?.name}
publicKey={getNpub(item.id)}
validNip05={item?.valid_nip05}
nip05={item?.nip05}
lnurl={item?.lnurl}
lnAddress={item?.ln_address}
picture={item?.picture}
/>
</View>
<View style={styles.zapperAmount}>
<MaterialCommunityIcons
style={styles.zapperAmountIcon}
name='lightning-bolt'
size={15}
color={'#F5D112'}
/>
<Text>
{item.amount} {getSatoshiSymbol(15)}
</Text>
</View>
</View>
</TouchableRipple>
)
}
const rbSheetCustomStyles = React.useMemo(() => {
return {
container: {
@ -78,15 +188,28 @@ export const LnPayment: React.FC<LnPaymentProps> = ({ open, setOpen, note, user
}
}, [])
return lnurl ? (
return (
<>
<RBSheet
ref={bottomSheetLnPaymentRef}
closeOnDragDown={true}
customStyles={rbSheetCustomStyles}
onClose={() => setOpen(false)}
onClose={() => {
relayPool?.unsubscribe(['zappers-meta'])
setOpen(false)
}}
>
<View style={styles.drawerBottom}>
{zaps.length > 0 && (
<View style={styles.zappers}>
<View style={styles.zappersList}>
<ScrollView>
<FlatList data={zaps} renderItem={renderZapperItem} />
</ScrollView>
</View>
<Divider />
</View>
)}
<View style={[styles.montoSelection, styles.spacer]}>
<Button style={styles.montoButton} mode='outlined' onPress={() => setMonto('1000')}>
<Text>1k {getSatoshiSymbol(15)}</Text>
@ -117,20 +240,27 @@ export const LnPayment: React.FC<LnPaymentProps> = ({ open, setOpen, note, user
style={styles.spacer}
mode='contained'
disabled={loading || monto === ''}
onPress={() => generateInvoice()}
loading={loading}
onPress={() => generateInvoice(false)}
loading={loading && !isZap}
>
{t('lnPayment.generateInvoice')}
{t('lnPayment.anonTip')}
</Button>
<Button
style={styles.spacer}
mode='contained'
disabled={loading || monto === ''}
onPress={() => generateInvoice(true)}
loading={loading && isZap}
>
{t('lnPayment.zap')}
</Button>
<Button mode='outlined' onPress={() => setOpen(false)}>
{t('lnPayment.cancel')}
</Button>
</View>
</RBSheet>
<LnPreview invoice={invoice} setInvoice={setInvoice} />
<LnPreview invoice={invoice} setInvoice={setInvoice} setOpen={setOpen} />
</>
) : (
<></>
)
}
@ -154,6 +284,13 @@ const styles = StyleSheet.create({
fontFamily: 'Satoshi-Symbol',
fontSize: 20,
},
zappersList: {
maxHeight: 200,
marginBottom: 16,
},
zappers: {
marginBottom: 16,
},
montoSelection: {
flexDirection: 'row',
},
@ -174,6 +311,20 @@ const styles = StyleSheet.create({
alignItems: 'center',
padding: 16,
},
zapperRow: {
padding: 16,
flexDirection: 'row',
justifyContent: 'space-between',
},
zapperData: {
flex: 1,
},
zapperAmount: {
flexDirection: 'row',
},
zapperAmountIcon: {
paddingTop: 3,
},
})
export default LnPayment

View File

@ -9,11 +9,12 @@ import { AppContext } from '../../Contexts/AppContext'
import { decode, PaymentRequestObject, TagsObject } from 'bolt11'
interface LnPreviewProps {
setOpen: (open: boolean) => void
invoice?: string
setInvoice: (invoice: string | undefined) => void
}
export const LnPreview: React.FC<LnPreviewProps> = ({ invoice, setInvoice }) => {
export const LnPreview: React.FC<LnPreviewProps> = ({ invoice, setInvoice, setOpen }) => {
const theme = useTheme()
const { t } = useTranslation('common')
const { getSatoshiSymbol } = React.useContext(AppContext)
@ -63,7 +64,10 @@ export const LnPreview: React.FC<LnPreviewProps> = ({ invoice, setInvoice }) =>
closeOnDragDown={true}
// height={630}
customStyles={rbSheetQrCustomStyles}
onClose={() => setInvoice(undefined)}
onClose={() => {
setInvoice(undefined)
setOpen(false)
}}
>
<Card style={styles.qrContainer}>
<Card.Content>

View File

@ -22,8 +22,18 @@ import ProfileData from '../ProfileData'
export const MenuItems: React.FC = () => {
const [drawerItemIndex, setDrawerItemIndex] = React.useState<number>(-1)
const { relays } = React.useContext(RelayPoolContext)
const { nPub, publicKey, privateKey, logout, name, picture, validNip05, lnurl, nip05 } =
React.useContext(UserContext)
const {
nPub,
publicKey,
privateKey,
logout,
name,
picture,
validNip05,
lnurl,
lnAddress,
nip05,
} = React.useContext(UserContext)
const { t } = useTranslation('common')
const theme = useTheme()
@ -83,7 +93,8 @@ export const MenuItems: React.FC = () => {
publicKey={publicKey}
validNip05={validNip05}
nip05={nip05}
lud06={lnurl}
lnurl={lnurl}
lnAddress={lnAddress}
picture={picture}
/>
</TouchableRipple>

View File

@ -10,7 +10,8 @@ interface NostrosAvatarProps {
src?: string
name?: string
size?: number
lud06?: string
lnurl?: string
lnAddress?: string
}
export const NostrosAvatar: React.FC<NostrosAvatarProps> = ({
@ -18,11 +19,13 @@ export const NostrosAvatar: React.FC<NostrosAvatarProps> = ({
name,
pubKey,
size = 40,
lud06,
lnurl,
lnAddress,
}) => {
const theme = useTheme()
const displayName = name && name !== '' ? name : formatPubKey(pubKey) ?? ''
const hasLud06 = lud06 && lud06 !== ''
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const hasLud06 = (lnurl && lnurl !== '') || (lnAddress && lnAddress !== '')
const lud06IconSize = size / 2.85
return (

View File

@ -36,10 +36,11 @@ import { REGEX_SOCKET_LINK } from '../../Constants/Relay'
import { navigate, push } from '../../lib/Navigation'
import { Kind } from 'nostr-tools'
import ProfileData from '../ProfileData'
import { relayToColor } from '../../Functions/NativeFunctions'
import { formatBigNumber, relayToColor } from '../../Functions/NativeFunctions'
import { SvgXml } from 'react-native-svg'
import { reactionIcon } from '../../Constants/Theme'
import LnPayment from '../LnPayment'
import { getZapsAmount } from '../../Functions/DatabaseFunctions/Zaps'
interface NoteCardProps {
note?: Note
@ -75,6 +76,7 @@ export const NoteCard: React.FC<NoteCardProps> = ({
const [userDownvoted, setUserDownvoted] = useState<boolean>(false)
const [repliesCount, setRepliesCount] = React.useState<number>(0)
const [repostCount, serRepostCount] = React.useState<number>(0)
const [zapsAmount, setZapsAmount] = React.useState<number>()
const [relays, setRelays] = React.useState<NoteRelay[]>([])
const [hide, setHide] = useState<boolean>(isContentWarning(note))
const [userReposted, setUserReposted] = useState<boolean>()
@ -102,6 +104,9 @@ export const NoteCard: React.FC<NoteCardProps> = ({
getRepliesCount(database, note.id).then(setRepliesCount)
getRepostCount(database, note.id).then(serRepostCount)
isUserReposted(database, note.id, publicKey).then(setUserReposted)
if (note.zap_pubkey?.length > 0) {
getZapsAmount(database, note.id).then(setZapsAmount)
}
}
getNoteRelays(database, note.id).then(setRelays)
}
@ -372,7 +377,8 @@ export const NoteCard: React.FC<NoteCardProps> = ({
publicKey={note.pubkey}
validNip05={note?.valid_nip05}
nip05={note?.nip05}
lud06={note?.lnurl}
lnurl={note?.lnurl}
lnAddress={note?.ln_address}
picture={showAvatarImage ? note?.picture : undefined}
timestamp={note?.created_at}
/>
@ -415,20 +421,18 @@ export const NoteCard: React.FC<NoteCardProps> = ({
{showActionCount && reactionsCount()}
</Button>
</Surface>
{note.lnurl && (
<>
<Button
style={styles.action}
icon={() => (
<MaterialCommunityIcons name='lightning-bolt' size={24} color={'#F5D112'} />
)}
onPress={() => setOpenLn(true)}
>
{''}
</Button>
{openLn && <LnPayment open={openLn} setOpen={setOpenLn} note={note} />}
</>
{(note?.lnurl || note?.ln_address) && (
<Button
style={styles.action}
icon={() => (
<MaterialCommunityIcons name='lightning-bolt' size={24} color={'#F5D112'} />
)}
onPress={() => setOpenLn(true)}
>
{note.zap_pubkey?.length > 0 ? formatBigNumber(zapsAmount) : ''}
</Button>
)}
{openLn && <LnPayment open={openLn} setOpen={setOpenLn} note={note} />}
</Card.Content>
)}
<Card.Content style={styles.relayList}>

View File

@ -221,7 +221,9 @@ export const ProfileActions: React.FC<ProfileActionsProps> = ({
icon='lightning-bolt'
size={28}
onPress={() => setOpenLn(true)}
disabled={!user?.lnurl}
disabled={
!user.lnurl && user.lnurl !== '' && !user?.ln_address && user.ln_address !== ''
}
iconColor='#F5D112'
/>
<Text>{t('profileCard.invoice')}</Text>

View File

@ -61,7 +61,8 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({ bottomSheetRef, showIm
publicKey={user?.id ?? displayUserDrawer}
validNip05={user?.valid_nip05}
nip05={user?.nip05}
lud06={user?.lnurl}
lnurl={user?.lnurl}
lnAddress={user?.ln_address}
picture={showImages ? user?.picture : undefined}
/>
</View>

View File

@ -10,8 +10,9 @@ import { getNpub } from '../../lib/nostr/Nip19'
interface ProfileCardProps {
username?: string
publicKey?: string
lud06?: string
validNip05?: boolean
lnurl?: string
lnAddress?: string
validNip05?: number
nip05?: string
picture?: string
avatarSize?: number
@ -21,7 +22,8 @@ interface ProfileCardProps {
export const ProfileData: React.FC<ProfileCardProps> = ({
username,
publicKey,
lud06,
lnurl,
lnAddress,
validNip05,
nip05,
picture,
@ -35,6 +37,7 @@ export const ProfileData: React.FC<ProfileCardProps> = ({
timestamp ? formatDistance(fromUnixTime(timestamp), new Date(), { addSuffix: true }) : null,
[timestamp],
)
return (
<View style={styles.container}>
<View style={styles.left}>
@ -42,7 +45,8 @@ export const ProfileData: React.FC<ProfileCardProps> = ({
name={username}
pubKey={nPub}
src={picture}
lud06={lud06}
lnurl={lnurl}
lnAddress={lnAddress}
size={avatarSize}
/>
<View style={[styles.contactData, { height: avatarSize }]}>

View File

@ -113,6 +113,7 @@ export const UploadImage: React.FC<UploadImageProps> = ({
mode='contained'
onPress={uploadImage}
loading={uploadingFile}
disabled={uploadingFile}
>
{t('uploadImage.uploadImage')}
</Button>

View File

@ -32,6 +32,8 @@ export interface UserContextProps {
nip05?: string
setNip05: (value: string) => void
validNip05?: boolean
setLnAddress: (value: string) => void
lnAddress?: string
}
export interface UserContextProviderProps {
@ -49,6 +51,7 @@ export const initialUserContext: UserContextProps = {
setPicture: () => {},
setAbout: () => {},
setLnurl: () => {},
setLnAddress: () => {},
setNip05: () => {},
}
@ -64,6 +67,7 @@ export const UserContextProvider = ({ children }: UserContextProviderProps): JSX
const [picture, setPicture] = useState<string>()
const [about, setAbout] = useState<string>()
const [lnurl, setLnurl] = useState<string>()
const [lnAddress, setLnAddress] = useState<string>()
const [nip05, setNip05] = useState<string>()
const [validNip05, setValidNip05] = useState<boolean>()
@ -75,6 +79,7 @@ export const UserContextProvider = ({ children }: UserContextProviderProps): JSX
setPicture(result.picture)
setAbout(result.about)
setLnurl(result.lnurl)
setLnAddress(result.ln_address)
setNip05(result.nip05)
setValidNip05(result.valid_nip05)
}
@ -93,6 +98,7 @@ export const UserContextProvider = ({ children }: UserContextProviderProps): JSX
setPicture(undefined)
setAbout(undefined)
setLnurl(undefined)
setLnAddress(undefined)
setNip05(undefined)
setValidNip05(undefined)
dropTables(database).then(() => {
@ -181,6 +187,8 @@ export const UserContextProvider = ({ children }: UserContextProviderProps): JSX
nip05,
setNip05,
validNip05,
lnAddress,
setLnAddress,
}}
>
{children}

View File

@ -6,10 +6,12 @@ export interface Note extends Event {
name: string
picture: string
lnurl: string
ln_address: string
reply_event_id: string
user_created_at: number
nip05: string
valid_nip05: boolean
valid_nip05: number
zap_pubkey: string
repost_id: string
blocked: number
}
@ -41,7 +43,9 @@ export const getMainNotes: (
) => Promise<Note[]> = async (db, pubKey, limit, contants, filters) => {
let notesQuery = `
SELECT
nostros_notes.*, nostros_users.nip05, nostros_users.blocked, nostros_users.valid_nip05, nostros_users.lnurl, nostros_users.name, nostros_users.picture, nostros_users.contact, nostros_users.created_at as user_created_at FROM nostros_notes
nostros_notes.*, nostros_users.zap_pubkey, nostros_users.nip05, nostros_users.blocked, nostros_users.valid_nip05,
nostros_users.ln_address, nostros_users.lnurl, nostros_users.name, nostros_users.picture, nostros_users.contact,
nostros_users.created_at as user_created_at FROM nostros_notes
LEFT JOIN
nostros_users ON nostros_users.id = nostros_notes.pubkey
WHERE
@ -111,7 +115,9 @@ export const getMentionNotes: (
) => Promise<Note[]> = async (db, pubKey, limit) => {
const notesQuery = `
SELECT
nostros_notes.*, nostros_users.nip05, nostros_users.valid_nip05, nostros_users.lnurl, nostros_users.name, nostros_users.picture, nostros_users.contact, nostros_users.created_at as user_created_at FROM nostros_notes
nostros_notes.*, nostros_users.zap_pubkey, nostros_users.nip05, nostros_users.valid_nip05, nostros_users.lnurl,
nostros_users.ln_address, nostros_users.name, nostros_users.picture, nostros_users.contact,
nostros_users.created_at as user_created_at FROM nostros_notes
LEFT JOIN
nostros_users ON nostros_users.id = nostros_notes.pubkey
WHERE (nostros_notes.reply_event_id IN (
@ -136,7 +142,9 @@ export const getReactedNotes: (
) => Promise<Note[]> = async (db, pubKey, limit) => {
const notesQuery = `
SELECT
nostros_notes.*, nostros_users.nip05, nostros_users.valid_nip05, nostros_users.lnurl, nostros_users.name, nostros_users.picture, nostros_users.contact, nostros_users.created_at as user_created_at FROM nostros_notes
nostros_notes.*, nostros_users.zap_pubkey, nostros_users.nip05, nostros_users.valid_nip05, nostros_users.lnurl,
nostros_users.ln_address, nostros_users.name, nostros_users.picture, nostros_users.contact,
nostros_users.created_at as user_created_at FROM nostros_notes
LEFT JOIN
nostros_users ON nostros_users.id = nostros_notes.pubkey
WHERE nostros_notes.id IN (
@ -160,7 +168,9 @@ export const getRepostedNotes: (
) => Promise<Note[]> = async (db, pubKey, limit) => {
const notesQuery = `
SELECT
nostros_notes.*, nostros_users.nip05, nostros_users.valid_nip05, nostros_users.lnurl, nostros_users.name, nostros_users.picture, nostros_users.contact, nostros_users.created_at as user_created_at FROM nostros_notes
nostros_notes.*, nostros_users.zap_pubkey, nostros_users.nip05, nostros_users.valid_nip05, nostros_users.lnurl,
nostros_users.ln_address, nostros_users.name, nostros_users.picture, nostros_users.contact,
nostros_users.created_at as user_created_at FROM nostros_notes
LEFT JOIN
nostros_users ON nostros_users.id = nostros_notes.pubkey
WHERE nostros_notes.repost_id IS NOT NULL AND
@ -297,7 +307,9 @@ export const getNotes: (
) => Promise<Note[]> = async (db, { filters = {}, limit, contacts, includeIds }) => {
let notesQuery = `
SELECT
nostros_notes.*, nostros_users.nip05, nostros_users.valid_nip05, nostros_users.lnurl, nostros_users.name, nostros_users.picture, nostros_users.contact, nostros_users.created_at as user_created_at FROM nostros_notes
nostros_notes.*, nostros_users.zap_pubkey, nostros_users.nip05, nostros_users.valid_nip05,
nostros_users.ln_address, nostros_users.lnurl, nostros_users.name, nostros_users.picture,
nostros_users.contact, nostros_users.created_at as user_created_at FROM nostros_notes
LEFT JOIN
nostros_users ON nostros_users.id = nostros_notes.pubkey
`

View File

@ -10,11 +10,13 @@ export interface User {
contact?: boolean
follower?: number
lnurl?: string
ln_address?: string
nip05?: string
created_at?: number
valid_nip05?: boolean
blocked?: number
muted_groups?: number
zap_pubkey?: string
}
const databaseToEntity: (object: object) => User = (object) => {

View File

@ -0,0 +1,60 @@
import { QuickSQLiteConnection } from 'react-native-quick-sqlite'
import { getItems } from '..'
import { Event } from '../../../lib/nostr/Events'
export interface Zap extends Event {
amount: boolean
zapped_event_id: string
zapped_user_id: string
zapper_user_id: string
name: string
picture: string
user_id: string
valid_nip05: number
nip05: string
lnurl: string
ln_address: string
}
const databaseToEntity: (object: object) => Zap = (object) => {
return object as Zap
}
export const getZapsAmount: (
db: QuickSQLiteConnection,
eventId: string,
) => Promise<number> = async (db, eventId) => {
const zapsQuery = `
SELECT
SUM(amount)
FROM
nostros_zaps
WHERE zapped_event_id = "${eventId}"
`
const resultSet = db.execute(zapsQuery)
const item: { 'SUM(amount)': number } = resultSet?.rows?.item(0)
return item['SUM(amount)'] ?? 0
}
export const getZaps: (db: QuickSQLiteConnection, eventId: string) => Promise<Zap[]> = async (
db,
eventId,
) => {
const groupsQuery = `
SELECT
nostros_zaps.*, nostros_users.name, nostros_users.id as user_id, nostros_users.picture, nostros_users.valid_nip05,
nostros_users.nip05, nostros_users.lnurl, nostros_users.ln_address
FROM
nostros_zaps
LEFT JOIN
nostros_users ON nostros_users.id = nostros_zaps.zapper_user_id
WHERE zapped_event_id = "${eventId}"
`
const resultSet = await db.execute(groupsQuery)
const items: object[] = getItems(resultSet)
const notes: Zap[] = items.map((object) => databaseToEntity(object))
return notes
}

View File

@ -81,6 +81,18 @@ export const validNip21: (string: string | undefined) => boolean = (string) => {
}
}
export const formatBigNumber: (num: number | undefined) => string = (num) => {
if (num === undefined) return ''
if (num >= 1_000_000) {
return `${(num / 1_000_000).toFixed(1)}M`
} else if (num >= 1_000) {
return `${(num / 1_000).toFixed(1)}K`
} else {
return num.toString()
}
}
export const randomInt: (min: number, max: number) => number = (min, max) =>
Math.floor(Math.random() * (max - min + 1)) + min

View File

@ -100,7 +100,9 @@
"generateInvoice": "Invoice generieren",
"cancel": "Abbrechen",
"copy": "Kopieren",
"open": "Öffne Wallet"
"open": "Öffne Wallet",
"anonTip": "Anonymous tip",
"zap": "Zap"
},
"notificationsFeed": {
"emptyTitle": "Keine Benachrichtigung.",
@ -263,15 +265,16 @@
"nsecCopied": "Privater Schlüssel kopiert.",
"npubCopied": "Öffentlicher Schlüssel kopiert.",
"profilePublished": "Profil veröffentlicht.",
"lud06Published": "LUD-06 veröffentlicht.\n\n{{lud06}}",
"lud06Published": "LUDs veröffentlicht.",
"nip05Published": "NIP-05 veröffentlicht.\n\n{{nip05}}",
"picturePublished": "Bild veröffentlicht.",
"connectionError": "Verbindungsfehler"
},
"publishLud06": "Veröffentlicht",
"lud06Label": "LNURL / Lightning Addresse",
"lud06Label": "LNURL",
"lud16Label": "Lightning Addresse",
"lud06Description": "Profil verknüpfen mit LNURL oder Lightning Addresse.",
"lud06Title": "LUD-06",
"lud06Title": "Zaps",
"publishNip05": "Veröffentlichen",
"nip05Link": "Mehr erfahren",
"nip05Description": "Profil mit Domain verbinden.",
@ -289,7 +292,7 @@
"npub": "Öffentlicher Schlüssel",
"copyNPub": "Kopieren",
"directory": "Verzeichnis",
"lud06": "LUD-06",
"lud06": "Zaps",
"nip05": "NIP-05",
"name": "Name",
"about": "Über mich"

View File

@ -101,7 +101,9 @@
"cancel": "Cancel",
"npub": "Public key",
"copy": "Copy",
"open": "Open wallet"
"open": "Open wallet",
"anonTip": "Anonymous tip",
"zap": "Zap"
},
"notificationsFeed": {
"emptyTitle": "You don't have any notifications.",
@ -261,15 +263,16 @@
"nsecCopied": "Private key copied.",
"npubCopied": "Public key copied.",
"profilePublished": "Profile published.",
"lud06Published": "LUD-06 published.\n\n{{lud06}}",
"lud06Published": "LUDs published.",
"nip05Published": "NIP-05 published.\n\n{{nip05}}",
"picturePublished": "Picture published.",
"connectionError": "Connection error."
},
"publishLud06": "Publish",
"lud06Label": "LNURL / Lightning Address",
"lud06Label": "LNURL",
"lud16Label": "Lightning Address",
"lud06Description": "Link your identity with an LNURL or Lightning Address.",
"lud06Title": "LUD-06",
"lud06Title": "Zaps",
"publishNip05": "Publish",
"nip05Link": "Learn more.",
"nip05Description": "Link your identity with a domain.",
@ -287,7 +290,7 @@
"npub": "Public key",
"copyNPub": "Copy",
"directory": "Directory",
"lud06": "LUD-06",
"lud06": "Zaps",
"nip05": "NIP-05",
"name": "Name",
"about": "Description"

View File

@ -121,7 +121,9 @@
"generateInvoice": "Generar factura",
"cancel": "Cancelar",
"copy": "Copiar",
"open": "Abrir wallet"
"open": "Abrir wallet",
"anonTip": "Propina anónima",
"zap": "Zap"
},
"notificationsFeed": {
"emptyTitle": "No tienes notificationes.",
@ -267,15 +269,16 @@
"nsecCopied": "Clave secreta copiada.",
"npubCopied": "Clave pública copiada.",
"profilePublished": "Perfil publicado.",
"lud06Published": "LUD-06 publicado.\n\n{{lud06}}",
"lud06Published": "LUDs publicados.",
"nip05Published": "NIP-05 publicado.\n\n{{nip05}}",
"picturePublished": "Imagen publicada.",
"connectionError": "Error de conexión"
},
"publishLud06": "Publicar",
"lud06Label": "LNURL / Lightning Address",
"lud06Label": "LNURL",
"lud16Label": "Lightning Address",
"lud06Description": "Vincula tu perfil a LNURL or Lightning Address.",
"lud06Title": "LUD-06",
"lud06Title": "Zaps",
"publishNip05": "Publicar",
"nip05Link": "Saber más.",
"nip05Description": "Vincula tu perfil con un dominio.",
@ -293,7 +296,7 @@
"npub": "Clave pública",
"copyNPub": "Copiar",
"directory": "Directory",
"lud06": "LUD-06",
"lud06": "Zaps",
"nip05": "NIP-05",
"name": "Nombre",
"about": "Descripción"

View File

@ -126,7 +126,9 @@
"generateInvoice": "Générer une facture",
"cancel": "Annuler",
"copy": "Copier",
"open": "Ouvrir le wallet"
"open": "Ouvrir le wallet",
"anonTip": "Anonymous tip",
"zap": "Zap"
},
"notificationsFeed": {
"emptyTitle": "Vous n'avez pas de notification.",
@ -265,15 +267,16 @@
"nsecCopied": "Clé privée copiée",
"npubCopied": "Clé publique copiée",
"profilePublished": "Votre profil a été publié",
"lud06Published": "LUD-06 publié.\n\n{{lud06}}",
"lud06Published": "LUDs publié.",
"nip05Published": "NIP-05 publié.\n\n{{nip05}}",
"picturePublished": "Image publiée.",
"connectionError": "Erreur de connexion"
},
"publishLud06": "Publier",
"lud06Label": "LNURL / Adresse Lightning",
"lud06Label": "LNURL",
"lud16Label": "Adresse Lightning",
"lud06Description": "Associez votre profil à LNURL ou à l'adresse Lightning.",
"lud06Title": "LUD-06",
"lud06Title": "Zaps",
"publishNip05": "Publier",
"nip05Link": "En savoir plus.",
"nip05Description": "Associez votre profil à un domaine.",
@ -291,7 +294,7 @@
"npub": "Clé publique",
"copyNPub": "Copier",
"directory": "Directory",
"lud06": "LUD-06",
"lud06": "Zaps",
"nip05": "NIP-05",
"name": "Nom",
"about": "Description"

View File

@ -122,7 +122,9 @@
"cancel": "Отменить",
"npub": "Публичный ключ",
"copy": "Скопировать",
"open": "Открыть кошелек"
"open": "Открыть кошелек",
"anonTip": "Anonymous tip",
"zap": "Zap"
},
"notificationsFeed": {
"emptyTitle": "У Вас нет новых уведомлений",
@ -259,7 +261,7 @@
"nsecCopied": "Приватный ключ скопирован",
"npubCopied": "Публичный ключ скопирован",
"profilePublished": "Profile published.",
"lud06Published": "LUD-06 published.\n\n{{lud06}}",
"lud06Published": "LUDs published.",
"nip05Published": "NIP-05 published.\n\n{{nip05}}",
"picturePublished": "Picture published.",
"connectionError": "Ошибка с подключением",
@ -267,9 +269,10 @@
"desactive": "Relay desactivated."
},
"publishLud06": "Опубликовть",
"lud06Label": "LNURL / Lightning Address",
"lud06Label": "LNURL",
"lud16Label": "Lightning Address",
"lud06Description": "Link your identity with a LNURL or Lightning Address.",
"lud06Title": "LUD-06",
"lud06Title": "Zaps",
"publishNip05": "Publish",
"nip05Link": "Подробнее.",
"nip05Description": "Link your identity with a domain.",
@ -287,7 +290,7 @@
"npub": "Публичный ключ",
"copyNPub": "Скопировать",
"directory": "Directory",
"lud06": "LUD-06",
"lud06": "Zaps",
"nip05": "NIP-05",
"name": "Name",
"about": "Описание"

View File

@ -120,7 +120,9 @@
"cancel": "取消",
"npub": "公钥",
"copy": "复制",
"open": "打开钱包"
"open": "打开钱包",
"anonTip": "Anonymous tip",
"zap": "Zap"
},
"notificationsFeed": {
"emptyTitle": "还没有新通知",
@ -272,7 +274,7 @@
"nsecCopied": "已复制私钥",
"npubCopied": "已复制公钥",
"profilePublished": "简介已发布",
"lud06Published": "LUD-06 已发布 \n\n{{lud06}}",
"lud06Published": "LUDs 已发布",
"nip05Published": "NIP-05 已发布 \n\n{{nip05}}",
"picturePublished": "头像已发布",
"connectionError": "连接错误",
@ -280,9 +282,10 @@
"desactive": "中继已停用"
},
"publishLud06": "发布",
"lud06Label": "LNURL / Lightning 地址",
"lud06Label": "LNURL",
"lud16Label": "Lightning 地址",
"lud06Description": "链接您的身份到 LNURL 或 Lightning 地址",
"lud06Title": "LUD-06",
"lud06Title": "Zaps",
"publishNip05": "发布",
"nip05Link": "了解更多",
"nip05Description": "链接接到域名",
@ -300,7 +303,7 @@
"npub": "公钥",
"copyNPub": "复制",
"directory": "目录",
"lud06": "LUD-06",
"lud06": "Zaps",
"nip05": "NIP-05",
"name": "用户名",
"about": "简介"

View File

@ -85,7 +85,7 @@ export const ContactsPage: React.FC = () => {
relayPool?.subscribe('contacts-meta', [
{
kinds: [Kind.Metadata],
authors: results.map((user) => user.id),
authors: following.map((user) => user.id),
},
])
}
@ -196,7 +196,8 @@ export const ContactsPage: React.FC = () => {
publicKey={getNpub(item.id)}
validNip05={item?.valid_nip05}
nip05={item?.nip05}
lud06={item?.lnurl}
lnurl={item?.lnurl}
lnAddress={item?.ln_address}
picture={item?.picture}
/>
</View>
@ -225,7 +226,8 @@ export const ContactsPage: React.FC = () => {
publicKey={getNpub(item.id)}
validNip05={item?.valid_nip05}
nip05={item?.nip05}
lud06={item?.lnurl}
lnurl={item?.lnurl}
lnAddress={item?.ln_address}
picture={item?.picture}
/>
</View>

View File

@ -141,7 +141,8 @@ export const ConversationsFeed: React.FC = () => {
publicKey={user.id}
validNip05={user?.valid_nip05}
nip05={user?.nip05}
lud06={user?.lnurl}
lnurl={user?.lnurl}
lnAddress={user?.ln_address}
picture={user?.picture}
/>
</View>
@ -240,7 +241,8 @@ export const ConversationsFeed: React.FC = () => {
publicKey={item.id}
validNip05={item?.valid_nip05}
nip05={item?.nip05}
lud06={item?.lnurl}
lnurl={item?.lnurl}
lnAddress={item?.ln_address}
picture={item?.picture}
/>
</View>

View File

@ -85,8 +85,8 @@ export const GroupPage: React.FC<GroupPageProps> = ({ route }) => {
const loadGroupMessages: (subscribe: boolean) => void = (subscribe) => {
if (database && publicKey && route.params.groupId) {
getGroup(database, route.params.groupId).then(setGroup)
updateGroupRead(database, route.params.groupId)
getGroup(database, route.params.groupId).then(setGroup)
getGroupMessages(database, route.params.groupId, {
order: 'DESC',
limit: pageSize,
@ -175,7 +175,8 @@ export const GroupPage: React.FC<GroupPageProps> = ({ route }) => {
publicKey={item?.id}
validNip05={item?.valid_nip05}
nip05={item?.nip05}
lud06={item?.lnurl}
lnurl={item?.lnurl}
lnAddress={item?.ln_address}
picture={item?.picture}
/>
</View>

View File

@ -57,22 +57,25 @@ export const HomePage: React.FC = () => {
{
kinds: [Kind.ChannelMessage],
'#e': [publicKey],
limit: 20,
limit: 30,
},
{
kinds: [Kind.EncryptedDirectMessage],
'#p': [publicKey],
since: directMessageResults[0]?.created_at ?? 0,
limit: 30,
},
{
kinds: [Kind.Text],
'#p': [publicKey],
since: mentionResults[0]?.created_at ?? 0,
limit: 30,
},
{
kinds: [Kind.Text],
'#e': [publicKey],
since: mentionResults[0]?.created_at ?? 0,
limit: 30,
},
])
})

View File

@ -8,7 +8,7 @@ import {
View,
} from 'react-native'
import { AppContext } from '../../Contexts/AppContext'
import { getLastReply, getMainNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
import { getMainNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
import { UserContext } from '../../Contexts/UserContext'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
@ -18,7 +18,6 @@ import { ActivityIndicator, Button, Text } from 'react-native-paper'
import NoteCard from '../../Components/NoteCard'
import { useTheme } from '@react-navigation/native'
import { FlashList, ListRenderItem } from '@shopify/flash-list'
import { getLastReaction } from '../../Functions/DatabaseFunctions/Reactions'
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
import { useTranslation } from 'react-i18next'
@ -41,7 +40,6 @@ export const MyFeed: React.FC<MyFeedProps> = ({ navigation }) => {
const unsubscribe: () => void = () => {
relayPool?.unsubscribe([
'homepage-contacts-main',
'homepage-contacts-meta',
'homepage-contacts-replies',
'homepage-contacts-reactions',
'homepage-contacts-repost',
@ -104,32 +102,16 @@ export const MyFeed: React.FC<MyFeedProps> = ({ navigation }) => {
setNotes(notes)
if (notes.length > 0) {
const noteIds = notes.map((note) => note.id ?? '')
relayPool?.subscribe('homepage-contacts-meta', [
{
kinds: [Kind.Metadata],
authors: notes.map((note) => note.pubkey ?? ''),
},
])
const authors = notes.map((note) => note.pubkey ?? '')
const lastReaction = await getLastReaction(database, {
eventIds: notes.map((note) => note.id ?? ''),
})
relayPool?.subscribe('homepage-contacts-reactions', [
{
kinds: [Kind.Reaction],
'#e': noteIds,
since: lastReaction?.created_at ?? 0,
kinds: [Kind.Metadata],
authors,
},
])
const lastReply = await getLastReply(database, {
eventIds: notes.map((note) => note.id ?? ''),
})
relayPool?.subscribe('homepage-contacts-replies', [
{
kinds: [Kind.Text],
kinds: [Kind.Reaction, Kind.Text, 9735],
'#e': noteIds,
since: lastReply?.created_at ?? 0,
},
])
const repostIds = notes

View File

@ -90,7 +90,7 @@ export const NotePage: React.FC<NotePageProps> = ({ route }) => {
])
relayPool?.subscribe(`notepage-replies-${route.params.noteId.substring(0, 8)}`, [
{
kinds: [Kind.Reaction, Kind.Text, Kind.RecommendRelay],
kinds: [Kind.Reaction, Kind.Text, Kind.RecommendRelay, 9735],
'#e': [route.params.noteId],
},
])

View File

@ -8,7 +8,7 @@ import {
} from 'react-native'
import { AppContext } from '../../Contexts/AppContext'
import SInfo from 'react-native-sensitive-info'
import { getLastReply, getMentionNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
import { getMentionNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
import NoteCard from '../../Components/NoteCard'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
import { Kind } from 'nostr-tools'
@ -19,7 +19,6 @@ import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityI
import { useTranslation } from 'react-i18next'
import { navigate } from '../../lib/Navigation'
import { useFocusEffect } from '@react-navigation/native'
import { getLastReaction } from '../../Functions/DatabaseFunctions/Reactions'
import { getUnixTime } from 'date-fns'
import { Config } from '../../Functions/DatabaseFunctions/Config'
import { FlashList, ListRenderItem } from '@shopify/flash-list'
@ -46,7 +45,6 @@ export const NotificationsFeed: React.FC = () => {
'notification-feed',
'notification-replies',
'notification-reactions',
'notification-meta',
])
updateLastSeen()
}
@ -106,23 +104,15 @@ export const NotificationsFeed: React.FC = () => {
if (notes.length > 0) {
const notedIds = notes.map((note) => note.id ?? '')
const authors = notes.map((note) => note.pubkey ?? '')
const lastReaction = await getLastReaction(database, { eventIds: notedIds })
const lastReply = await getLastReply(database, { eventIds: notedIds })
relayPool?.subscribe('notification-meta', [
relayPool?.subscribe('notification-reactions', [
{
kinds: [Kind.Metadata],
authors,
},
{
kinds: [Kind.Reaction],
kinds: [Kind.Text, Kind.Reaction, 9735],
'#e': notedIds,
since: lastReaction?.created_at ?? 0,
},
{
kinds: [Kind.Text],
'#e': notedIds,
since: lastReply?.created_at ?? 0,
},
])
}

View File

@ -45,6 +45,8 @@ export const ProfileConfigPage: React.FC = () => {
setAbout,
lnurl,
setLnurl,
lnAddress,
setLnAddress,
nip05,
setNip05,
reloadUser,
@ -72,8 +74,8 @@ export const ProfileConfigPage: React.FC = () => {
)
useEffect(() => {
reloadUser()
if (isPublishingProfile) {
reloadUser()
setIsPublishingProfile(undefined)
setShowNotification(isPublishingProfile)
bottomSheetPictureRef.current?.close()
@ -91,6 +93,7 @@ export const ProfileConfigPage: React.FC = () => {
name,
about,
lud06: lnurl,
lud16: lnAddress,
nip05,
picture,
}),
@ -149,6 +152,12 @@ export const ProfileConfigPage: React.FC = () => {
})
}
const pasteLud16: () => void = () => {
Clipboard.getString().then((value) => {
setLnAddress(value ?? '')
})
}
return (
<View style={styles.container}>
<ScrollView horizontal={false} showsVerticalScrollIndicator={false}>
@ -164,7 +173,8 @@ export const ProfileConfigPage: React.FC = () => {
name={name}
pubKey={nPub ?? ''}
src={picture}
lud06={lnurl}
lnurl={lnurl}
lnAddress={lnAddress}
size={100}
/>
)}
@ -400,9 +410,23 @@ export const ProfileConfigPage: React.FC = () => {
/>
}
/>
<TextInput
style={styles.spacer}
mode='outlined'
multiline
label={t('profileConfigPage.lud16Label') ?? ''}
onChangeText={setLnAddress}
value={lnAddress}
right={
<TextInput.Icon
icon='content-paste'
onPress={pasteLud16}
forceTextInputFocus={false}
/>
}
/>
<Button
mode='contained'
disabled={!lnurl || lnurl === ''}
onPress={() => onPublishUser('lud06Published')}
loading={isPublishingProfile !== undefined}
>
@ -418,7 +442,11 @@ export const ProfileConfigPage: React.FC = () => {
onIconPress={() => setShowNotification(undefined)}
onDismiss={() => setShowNotification(undefined)}
>
{t(`profileConfigPage.notifications.${showNotification}`, { nip05, lud06: lnurl })}
{t(`profileConfigPage.notifications.${showNotification}`, {
nip05,
lud06: lnurl,
lud16: lnAddress,
})}
</Snackbar>
)}
<UploadImage

View File

@ -55,14 +55,15 @@ export const ProfileConnectPage: React.FC = () => {
setPrivateKey(key)
}
navigate('ProfileLoad')
} else if (loginMethod === 'mnemonic') {
const words = []
for (let index = 1; index <= 12; index++) {
words.push(mnemonicWords[index])
}
setPrivateKey(privateKeyFromSeedWords(words.join(' ')))
navigate('ProfileLoad')
}
} else if (loginMethod === 'mnemonic') {
const words = []
for (let index = 1; index <= 12; index++) {
words.push(mnemonicWords[index].trim())
}
setPrivateKey(privateKeyFromSeedWords(words.join(' ')))
setMnemonicWords({})
navigate('ProfileLoad')
}
}

View File

@ -30,11 +30,7 @@ export const ProfileLoadPage: React.FC = () => {
)
setTimeout(() => loadMeta(), 1000)
return () =>
relayPool?.unsubscribe([
'profile-load-meta',
'profile-load-notes',
'profile-load-meta-pets',
])
relayPool?.unsubscribe(['profile-load-meta', 'profile-load-notes', 'profile-load-others'])
}, []),
)
@ -58,7 +54,13 @@ export const ProfileLoadPage: React.FC = () => {
if (publicKey && relayPoolReady) {
relayPool?.subscribe('profile-load-meta', [
{
kinds: [Kind.Contacts, Kind.Metadata, Kind.ChannelCreation, Kind.ChannelMetadata, 1002],
kinds: [Kind.Metadata, Kind.Contacts],
authors: [publicKey],
},
])
relayPool?.subscribe('profile-load-others', [
{
kinds: [Kind.ChannelCreation, Kind.ChannelMetadata, 1002],
authors: [publicKey],
},
])

View File

@ -98,7 +98,7 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
if (results.length > 0) {
relayPool?.subscribe(`profile-answers${route.params.pubKey.substring(0, 8)}`, [
{
kinds: [Kind.Reaction, Kind.Text, Kind.RecommendRelay],
kinds: [Kind.Reaction, Kind.Text, Kind.RecommendRelay, 9735],
'#e': results.map((note) => note.id ?? ''),
},
])
@ -111,7 +111,11 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
const subscribeProfile: () => Promise<void> = async () => {
relayPool?.subscribe(`profile-user${route.params.pubKey.substring(0, 8)}`, [
{
kinds: [Kind.Metadata, Kind.Contacts],
kinds: [Kind.Metadata],
authors: [route.params.pubKey],
},
{
kinds: [Kind.Contacts],
authors: [route.params.pubKey],
},
])
@ -150,15 +154,20 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
>
<Surface style={styles.container} elevation={1}>
<View style={styles.profileData}>
<ProfileData
username={user?.name}
publicKey={route.params.pubKey}
validNip05={user?.valid_nip05}
nip05={user?.nip05}
lud06={user?.lnurl}
picture={user?.picture}
/>
<Text>{user?.follower && user.follower > 0 ? t('profilePage.isFollower') : ''}</Text>
<View style={styles.profilePicture}>
<ProfileData
username={user?.name}
publicKey={route.params.pubKey}
validNip05={user?.valid_nip05}
nip05={user?.nip05}
lnurl={user?.lnurl}
lnAddress={user?.ln_address}
picture={user?.picture}
/>
</View>
<View>
<Text>{user?.follower && user.follower > 0 ? t('profilePage.isFollower') : ''}</Text>
</View>
</View>
<View>
<Text>{user?.about}</Text>
@ -214,6 +223,9 @@ const styles = StyleSheet.create({
margin: 16,
bottom: 70,
},
profilePicture: {
width: '80%',
},
list: {
padding: 16,
},

View File

@ -8,7 +8,7 @@ import {
} from 'react-native'
import { FlashList, ListRenderItem } from '@shopify/flash-list'
import { AppContext } from '../../Contexts/AppContext'
import { getLastReply, getReactedNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
import { getReactedNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
import { UserContext } from '../../Contexts/UserContext'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
@ -19,7 +19,6 @@ import NoteCard from '../../Components/NoteCard'
import { useTheme } from '@react-navigation/native'
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
import { t } from 'i18next'
import { getLastReaction } from '../../Functions/DatabaseFunctions/Reactions'
interface ReactionsFeedProps {
navigation: any
@ -39,7 +38,6 @@ export const ReactionsFeed: React.FC<ReactionsFeedProps> = ({ navigation }) => {
const unsubscribe: () => void = () => {
relayPool?.unsubscribe([
'homepage-contacts-main',
'homepage-contacts-meta',
'homepage-contacts-replies',
'homepage-contacts-reactions',
'homepage-contacts-repost',
@ -99,33 +97,17 @@ export const ReactionsFeed: React.FC<ReactionsFeedProps> = ({ navigation }) => {
getReactedNotes(database, publicKey, pageSize).then(async (notes) => {
setNotes(notes)
if (notes.length > 0) {
const noteIds = notes.map((note) => note.id ?? '')
relayPool?.subscribe('homepage-contacts-meta', [
{
kinds: [Kind.Metadata],
authors: notes.map((note) => note.pubkey ?? ''),
},
])
const notedIds = notes.map((note) => note.id ?? '')
const authors = notes.map((note) => note.pubkey ?? '')
const lastReaction = await getLastReaction(database, {
eventIds: notes.map((note) => note.id ?? ''),
})
relayPool?.subscribe('homepage-contacts-reactions', [
{
kinds: [Kind.Reaction],
'#e': noteIds,
since: lastReaction?.created_at ?? 0,
kinds: [Kind.Metadata],
authors,
},
])
const lastReply = await getLastReply(database, {
eventIds: notes.map((note) => note.id ?? ''),
})
relayPool?.subscribe('homepage-contacts-replies', [
{
kinds: [Kind.Text],
'#e': noteIds,
since: lastReply?.created_at ?? 0,
kinds: [Kind.Text, Kind.Reaction, 9735],
'#e': notedIds,
},
])

View File

@ -42,7 +42,6 @@ export const RelaysPage: React.FC = () => {
{
kinds: [1002],
authors: [publicKey],
limit: 1,
},
])
}

View File

@ -8,7 +8,7 @@ import {
} from 'react-native'
import { FlashList, ListRenderItem } from '@shopify/flash-list'
import { AppContext } from '../../Contexts/AppContext'
import { getLastReply, getRepostedNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
import { getRepostedNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
import { UserContext } from '../../Contexts/UserContext'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
@ -19,7 +19,6 @@ import NoteCard from '../../Components/NoteCard'
import { useTheme } from '@react-navigation/native'
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
import { t } from 'i18next'
import { getLastReaction } from '../../Functions/DatabaseFunctions/Reactions'
interface RepostsFeedProps {
navigation: any
@ -39,7 +38,6 @@ export const RepostsFeed: React.FC<RepostsFeedProps> = ({ navigation }) => {
const unsubscribe: () => void = () => {
relayPool?.unsubscribe([
'homepage-contacts-main',
'homepage-contacts-meta',
'homepage-contacts-replies',
'homepage-contacts-reactions',
'homepage-contacts-repost',
@ -105,26 +103,9 @@ export const RepostsFeed: React.FC<RepostsFeedProps> = ({ navigation }) => {
kinds: [Kind.Metadata],
authors: notes.map((note) => note.pubkey ?? ''),
},
])
const lastReaction = await getLastReaction(database, {
eventIds: notes.map((note) => note.id ?? ''),
})
relayPool?.subscribe('homepage-contacts-reactions', [
{
kinds: [Kind.Reaction],
kinds: [Kind.Text, Kind.Reaction, 9735],
'#e': noteIds,
since: lastReaction?.created_at ?? 0,
},
])
const lastReply = await getLastReply(database, {
eventIds: notes.map((note) => note.id ?? ''),
})
relayPool?.subscribe('homepage-contacts-replies', [
{
kinds: [Kind.Text],
'#e': noteIds,
since: lastReply?.created_at ?? 0,
},
])
const repostIds = notes

View File

@ -137,7 +137,8 @@ export const SendPage: React.FC<SendPageProps> = ({ route }) => {
publicKey={item?.id}
validNip05={item?.valid_nip05}
nip05={item?.nip05}
lud06={item?.lnurl}
lnurl={item?.lnurl}
lnAddress={item?.ln_address}
picture={item?.picture}
/>
</View>

View File

@ -7278,11 +7278,6 @@ react-native-svg@^13.7.0:
css-select "^5.1.0"
css-tree "^1.1.3"
react-native-swipe-gestures@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/react-native-swipe-gestures/-/react-native-swipe-gestures-1.0.5.tgz#a172cb0f3e7478ccd681fd36b8bfbcdd098bde7c"
integrity sha512-Ns7Bn9H/Tyw278+5SQx9oAblDZ7JixyzeOczcBK8dipQk2pD7Djkcfnf1nB/8RErAmMLL9iXgW0QHqiII8AhKw==
react-native-tab-view@^3.3.4:
version "3.3.4"
resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-3.3.4.tgz#856d4527f3bbf05e2649302ec80abe9f2f004666"