Notification refactor (#487)

This commit is contained in:
KoalaSat 2023-04-05 16:35:07 +00:00 committed by GitHub
commit f4f48f919c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 346 additions and 192 deletions

View File

@ -243,6 +243,19 @@ public class Database {
try {
instance.execSQL("ALTER TABLE nostros_users ADD COLUMN tags TEXT;");
} catch (SQLException e) { }
try {
instance.execSQL("CREATE TABLE IF NOT EXISTS nostros_notifications(\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" +
" tags TEXT NOT NULL\n," +
" amount FLOAT,\n" +
" event_id TEXT\n" +
" );");
instance.execSQL("CREATE INDEX nostros_notifications_index ON nostros_notifications(created_at);");
} catch (SQLException e) { }
}
public void saveEvent(JSONObject data, String userPubKey, String relayUrl) throws JSONException {

View File

@ -67,7 +67,7 @@ public class Event {
} else if (kind.equals("4")) {
saveDirectMessage(database);
} else if (kind.equals("7")) {
saveReaction(database);
saveReaction(database, userPubKey);
} else if (kind.equals("40")) {
saveGroup(database);
} else if (kind.equals("41")) {
@ -81,7 +81,7 @@ public class Event {
} else if (kind.equals("10002")) {
saveRelayMetadata(database);
} else if (kind.equals("9735")) {
saveZap(database);
saveZap(database, userPubKey);
} else if (kind.equals("10000") || kind.equals("10001") || kind.equals("30001")) {
saveList(database);
}
@ -258,10 +258,33 @@ public class Event {
return "";
}
protected void saveNotification(SQLiteDatabase database, String eventId, double amount) {
String query = "SELECT id FROM nostros_notifications WHERE id = ?";
@SuppressLint("Recycle") Cursor cursor = database.rawQuery(query, new String[] {id});
if (cursor.getCount() == 0) {
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("tags", tags.toString());
values.put("amount", amount);
values.put("event_id", eventId);
database.replace("nostros_notifications", null, values);
}
}
protected void saveNote(SQLiteDatabase database, String userPubKey) {
String query = "SELECT id FROM nostros_notes WHERE id = ?";
@SuppressLint("Recycle") Cursor cursor = database.rawQuery(query, new String[] {id});
int userMentioned = getUserMentioned(userPubKey);
String repostId = getRepostId();
if (userMentioned > 0 && !pubkey.equals(userPubKey)) {
saveNotification(database, repostId, 0);
}
if (cursor.getCount() == 0) {
ContentValues values = new ContentValues();
values.put("id", id);
@ -273,8 +296,8 @@ public class Event {
values.put("tags", tags.toString());
values.put("main_event_id", getMainEventId());
values.put("reply_event_id", getReplyEventId());
values.put("user_mentioned", getUserMentioned(userPubKey));
values.put("repost_id", getRepostId());
values.put("user_mentioned", userMentioned);
values.put("repost_id", repostId);
database.replace("nostros_notes", null, values);
}
}
@ -480,7 +503,7 @@ public class Event {
}
}
protected void saveReaction(SQLiteDatabase database) throws JSONException {
protected void saveReaction(SQLiteDatabase database, String userPubKey) throws JSONException {
String query = "SELECT created_at FROM nostros_reactions WHERE id = ?";
@SuppressLint("Recycle") Cursor cursor = database.rawQuery(query, new String[] {id});
@ -497,6 +520,10 @@ public class Event {
reacted_user_id = pTags.getJSONArray(pTags.length() - 1).getString(1);
}
if (!pubkey.equals(userPubKey) && reacted_user_id.equals(userPubKey)) {
saveNotification(database, reacted_event_id, 0);
}
ContentValues values = new ContentValues();
values.put("id", id);
values.put("content", content);
@ -556,7 +583,7 @@ public class Event {
}
}
protected void saveZap(SQLiteDatabase database) throws JSONException {
protected void saveZap(SQLiteDatabase database, String userPubKey) throws JSONException {
String query = "SELECT created_at FROM nostros_zaps WHERE id = ?";
@SuppressLint("Recycle") Cursor cursor = database.rawQuery(query, new String[] {id});
@ -595,6 +622,10 @@ public class Event {
@SuppressLint("Recycle") Cursor userCursor = database.rawQuery(userQuery, new String[] {pubkey, zapped_user_id});
if (userCursor.moveToFirst()) {
if (zapped_user_id.equals(userPubKey) && !pubkey.equals(userPubKey)) {
saveNotification(database, zapped_event_id, amount);
}
ContentValues values = new ContentValues();
values.put("id", id);
values.put("content", content);

View File

@ -0,0 +1,49 @@
import { type Kind } from 'nostr-tools'
import { type QuickSQLiteConnection } from 'react-native-quick-sqlite'
import { getItems } from '..'
export interface Notification {
content: string
created_at: number
id?: string
kind: Kind | number
pubkey: string
amount: number
event_id?: string
tags: string[][]
name: string
}
const databaseToEntity: (object: any) => Notification = (object) => {
object.tags = object.tags ? JSON.parse(object.tags) : []
return object as Notification
}
export const getNotifications: (
db: QuickSQLiteConnection,
options: {
limit?: number
since?: number
},
) => Promise<Notification[]> = async (db, { limit, since }) => {
let notificationsQuery = `
SELECT
nostros_notifications.*, nostros_users.name
FROM
nostros_notifications
LEFT JOIN
nostros_users ON nostros_users.id = nostros_notifications.pubkey
`
if (since) {
notificationsQuery += `WHERE nostros_notifications.created_at > ${since} `
}
notificationsQuery += 'ORDER BY nostros_notifications.created_at DESC '
if (limit) {
notificationsQuery += `LIMIT ${limit} `
}
const resultSet = db.execute(notificationsQuery)
const items: object[] = getItems(resultSet)
const messages: Notification[] = items.map((object) => databaseToEntity(object))
return messages
}

View File

@ -38,6 +38,7 @@ export const dropTables: (db: QuickSQLiteConnection) => Promise<BatchQueryResult
['DELETE FROM nostros_direct_messages;', [[]]],
['DELETE FROM nostros_group_meta;', [[]]],
['DELETE FROM nostros_group_messages;', [[]]],
['DELETE FROM nostros_notifications;', [[]]],
]
return db.executeBatch(dropQueries)
}

View File

@ -130,6 +130,7 @@ export const GroupsFeed: React.FC = () => {
{
kinds: [Kind.ChannelMessage],
'#e': results.map((group) => group.id ?? ''),
limit: 30,
},
])
}

View File

@ -107,6 +107,13 @@ export const GlobalFeed: React.FC<GlobalFeedProps> = ({
setRefreshing(false)
if (results.length > 0) {
setNotes(results)
const message: RelayFilters[] = [
{
kinds: [Kind.Metadata],
authors: results.map((r) => r.pubkey),
},
]
relayPool?.subscribe('homepage-global-meta', message)
const repostIds = notes
.filter((note) => note.repost_id)
.map((note) => note.repost_id ?? '')

View File

@ -90,11 +90,13 @@ export const MyFeed: React.FC<MyFeedProps> = ({
setNotes(results)
setRefreshing(false)
relayPool?.subscribe('homepage-myfeed-main', [{
kinds: [Kind.Text, Kind.RecommendRelay],
authors: contacts,
limit: pageSize
}])
relayPool?.subscribe('homepage-myfeed-main', [
{
kinds: [Kind.Text, Kind.RecommendRelay],
authors: contacts,
limit: pageSize,
},
])
if (results.length > 0) {
const noteIds = results.map((note) => note.id ?? '')

View File

@ -72,7 +72,7 @@ export const ZapsFeed: React.FC<ZapsFeedProps> = ({
if (database && publicKey) {
const users: User[] = await getUsers(database, { contacts: true, order: 'created_at DESC' })
const contacts: string[] = [...users.map((user) => user.id), publicKey]
relayPool?.subscribe(`profile-zaps${publicKey}`, [
relayPool?.subscribe(`homepage-zaps`, [
{
kinds: [9735],
'#p': contacts,

View File

@ -32,6 +32,7 @@ export const HomeFeed: React.FC<HomeFeedProps> = ({ navigation }) => {
'homepage-zapped-notes',
'homepage-zapped-reactions',
'homepage-zapped-reposts',
'homepage-zaps',
])
}
if (activeTab !== 'myFeed') {
@ -42,7 +43,11 @@ export const HomeFeed: React.FC<HomeFeedProps> = ({ navigation }) => {
])
}
if (activeTab !== 'globalFeed') {
relayPool?.unsubscribe(['homepage-global-main', 'homepage-global-reposts'])
relayPool?.unsubscribe([
'homepage-global-main',
'homepage-global-reposts',
'homepage-global-meta',
])
}
}

View File

@ -8,7 +8,7 @@ import {
} from 'react-native'
import { AppContext, type Config } from '../../../Contexts/AppContext'
import SInfo from 'react-native-sensitive-info'
import { getMentionNotes, getNotes, type Note } from '../../../Functions/DatabaseFunctions/Notes'
import { type Note } from '../../../Functions/DatabaseFunctions/Notes'
import { RelayPoolContext } from '../../../Contexts/RelayPoolContext'
import { Kind } from 'nostr-tools'
import { UserContext } from '../../../Contexts/UserContext'
@ -18,31 +18,27 @@ import { navigate } from '../../../lib/Navigation'
import { useFocusEffect } from '@react-navigation/native'
import { format, fromUnixTime, getUnixTime } from 'date-fns'
import { FlashList, type ListRenderItem } from '@shopify/flash-list'
import { getETags } from '../../../Functions/RelayFunctions/Events'
import { getReactions, type Reaction } from '../../../Functions/DatabaseFunctions/Reactions'
import { getUsers, type User } from '../../../Functions/DatabaseFunctions/Users'
import { username } from '../../../Functions/RelayFunctions/Users'
import { TouchableWithoutFeedback } from 'react-native-gesture-handler'
import { getUserZaps, type Zap } from '../../../Functions/DatabaseFunctions/Zaps'
import { formatDate, handleInfinityScroll } from '../../../Functions/NativeFunctions'
import { useTranslation } from 'react-i18next'
import {
getNotifications,
type Notification,
} from '../../../Functions/DatabaseFunctions/Notifications'
import { getTaggedEventIds } from '../../../Functions/RelayFunctions/Events'
export const NotificationsFeed: React.FC = () => {
const initialLimitDate = React.useMemo(() => getUnixTime(new Date()) - 86400, [])
const initialLimitPage = React.useMemo(() => 20, [])
const theme = useTheme()
const { t } = useTranslation('common')
const { database, setNotificationSeenAt, pushedTab, getSatoshiSymbol } = useContext(AppContext)
const { publicKey, reloadLists, mutedEvents, mutedUsers } = useContext(UserContext)
const { lastEventId, relayPool } = useContext(RelayPoolContext)
const [pubKeys, setPubKeys] = React.useState<string[]>([])
const [users, setUsers] = React.useState<User[]>([])
const [userNotes, setUserNotes] = useState<Note[]>([])
const [mentionNotes, setMentionNotes] = useState<Note[]>([])
const [reactions, setReaction] = useState<Reaction[]>([])
const [zaps, setZaps] = useState<Zap[]>([])
const [notifications, setNotifications] = useState<Notification[]>([])
const [refreshing, setRefreshing] = useState(true)
const flashListRef = React.useRef<FlashList<Note>>(null)
const [limitDate, setLimitDate] = useState<number>(initialLimitDate)
const [limitPage, setLimitPage] = useState<number>(initialLimitPage)
useFocusEffect(
React.useCallback(() => {
@ -76,18 +72,6 @@ export const NotificationsFeed: React.FC = () => {
}
}, [pushedTab])
useEffect(() => {
if (database && pubKeys.length > 0) {
getUsers(database, { includeIds: pubKeys }).then(setUsers)
relayPool?.subscribe('notification-users', [
{
kinds: [Kind.Metadata],
authors: pubKeys.filter((key, index, array) => array.indexOf(key) === index),
},
])
}
}, [pubKeys])
const updateLastSeen: () => void = () => {
const unixtime = getUnixTime(new Date())
setNotificationSeenAt(unixtime)
@ -100,31 +84,16 @@ export const NotificationsFeed: React.FC = () => {
const subscribeNotes: () => void = async () => {
if (!publicKey || !database) return
getNotes(database, { filters: { pubkey: [publicKey] }, limitDate }).then((results) => {
if (results) {
setUserNotes(results)
const eventIds = results.map((e) => e.id ?? '')
if (eventIds.length > 0) {
relayPool?.subscribe('notification-reactions', [
{
kinds: [Kind.Reaction, 9735],
'#e': eventIds,
},
])
}
}
})
relayPool?.subscribe('notification-feed', [
{
kinds: [Kind.Text],
'#p': [publicKey],
since: limitDate,
limit: limitPage,
},
{
kinds: [Kind.Text],
'#e': [publicKey],
since: limitDate,
kinds: [Kind.Reaction, 9735],
'#p': [publicKey],
limit: limitPage,
},
{
kinds: [30001],
@ -135,29 +104,22 @@ export const NotificationsFeed: React.FC = () => {
const loadNotes: () => void = async () => {
if (database && publicKey) {
getReactions(database, { reactedUser: publicKey, limitDate }).then((results) => {
setPubKeys((prev) => [...prev, ...results.map((res) => res.pubkey)])
setReaction(results)
})
getUserZaps(database, publicKey, limitDate).then((results) => {
setZaps(results)
setPubKeys((prev) => [...prev, ...results.map((res) => res.zapper_user_id)])
})
getMentionNotes(database, publicKey, limitDate).then(async (notes) => {
const unmutedThreads = notes.filter((note) => {
if (!note?.id) return false
const eTags = getETags(note)
return (
!eTags.some((tag) => mutedEvents.includes(tag[1])) && !mutedUsers.includes(note.pubkey)
)
getNotifications(database, { limit: limitPage }).then((results) => {
const filtered = results.filter((event) => {
const eTags = getTaggedEventIds(event)
return !mutedUsers.includes(event.pubkey) && !mutedEvents.some((id) => eTags.includes(id))
})
setMentionNotes(unmutedThreads)
setRefreshing(false)
if (notes.length > 0) {
setPubKeys((prev) => [...prev, ...notes.map((note) => note.pubkey ?? '')])
}
if (notes.length < 5) {
setLimitDate(limitDate - 86400)
if (filtered.length > 0) {
setNotifications(filtered)
const pubKeys = filtered
.map((n) => n.pubkey)
.filter((key, index, array) => array.indexOf(key) === index)
relayPool?.subscribe('notification-users', [
{
kinds: [Kind.Metadata],
authors: pubKeys,
},
])
}
})
}
@ -171,41 +133,30 @@ export const NotificationsFeed: React.FC = () => {
}, [])
useEffect(() => {
if (limitDate < initialLimitDate) {
if (limitPage < initialLimitPage) {
loadNotes()
}
}, [limitDate])
}, [limitPage])
const generateItemVariables: (item: Note | Reaction | Zap) => {
user: User | undefined
eventId: string | undefined
content: string | undefined
const generateItemVariables: (item: Notification) => {
noteId: string | undefined
content: string
icon: string
iconColor: string
description: string
} = (item) => {
let note: Note | undefined
let user: User | undefined
let content: string | undefined
let eventId: string | undefined
let noteId: string | undefined = item.event_id
let content: string = item.content.replace(/nostr:\S*/, '').trim()
let icon: string
let iconColor: string
let description: string
if (item.kind === 9735) {
note = userNotes.find((note) => note.id === item.zapped_event_id)
user = users.find((user) => user.id === item.zapper_user_id)
const zapDescription = item.tags?.find((tag) => tag[0] === 'description')
content = zapDescription ? JSON.parse(zapDescription[1])?.content : ''
eventId = note?.id
icon = 'lightning-bolt'
iconColor = '#F5D112'
description = 'notificationsFeed.zap'
} else if (item.kind === Kind.Reaction) {
note = userNotes.find((note) => note.id === item.reacted_event_id)
user = users.find((user) => user.id === item.pubkey)
content = note?.content
eventId = note?.id
content = ''
if (item.content === '-') {
icon = 'thumb-down'
iconColor = theme.colors.error
@ -215,60 +166,54 @@ export const NotificationsFeed: React.FC = () => {
iconColor = theme.colors.onPrimaryContainer
description = 'notificationsFeed.like'
}
} else if (item.repost_id) {
note = userNotes.find((note) => note.id === item.repost_id)
user = users.find((user) => user.id === item.pubkey)
content = note?.content
eventId = note?.id
} else if (item.event_id) {
icon = 'cached'
iconColor = '#7ADC70'
description = 'notificationsFeed.reposted'
} else {
note = userNotes.find((note) => note.id === item.reacted_event_id)
user = users.find((user) => user.id === item.pubkey)
content = item?.content
eventId = item?.id
noteId = item.id
icon = 'message-outline'
iconColor = theme.colors.onPrimaryContainer
description = 'notificationsFeed.replied'
}
return {
eventId,
noteId,
content,
user,
icon,
iconColor,
description,
}
}
const renderItem: ListRenderItem<Note | Reaction | Zap> = ({ item }) => {
const renderItem: ListRenderItem<Notification> = ({ item }) => {
const date = fromUnixTime(item.created_at)
const { user, icon, iconColor, description, content, eventId } = generateItemVariables(item)
const { noteId, content, icon, iconColor, description } = generateItemVariables(item)
return (
<TouchableWithoutFeedback onPress={() => navigate('Note', { noteId: eventId })}>
<TouchableWithoutFeedback onPress={() => noteId && navigate('Note', { noteId })}>
<View style={styles.itemCard} key={item.id}>
<View style={styles.itemCardIcon}>
<MaterialCommunityIcons name={icon} size={25} color={iconColor} />
</View>
<View style={styles.itemCardInfo}>
<Text style={[styles.itemCardText, { color: theme.colors.onSurfaceVariant }]}>
{username(user)}
{username({ name: item.name, id: item.pubkey })}
</Text>
<Text style={styles.itemCardText}>
{t(description, { amount: item.amount ?? '' })}
{item.kind === 9735 && getSatoshiSymbol(16)}
</Text>
<Text
style={[styles.itemCardText, { color: theme.colors.onSurfaceVariant }]}
numberOfLines={
item.kind === 9735 || (item.kind === Kind.Text && !item.repost_id) ? undefined : 1
}
>
{content?.replace(/#\[\d\]/, '')}
</Text>
{content !== '' && (
<Text
style={[styles.itemCardText, { color: theme.colors.onSurfaceVariant }]}
numberOfLines={
item.kind === 9735 || (item.kind === Kind.Text && !item.event_id) ? undefined : 1
}
>
{content?.replace(/#\[\d\]/, '')}
</Text>
)}
</View>
<View style={styles.itemCardDates}>
<Text style={styles.itemCardDatesText}>{formatDate(item.created_at, false)}</Text>
@ -304,7 +249,7 @@ export const NotificationsFeed: React.FC = () => {
const onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void = (event) => {
if (handleInfinityScroll(event)) {
setLimitDate(limitDate - 86400)
setLimitPage(limitPage + initialLimitPage)
}
}
@ -312,7 +257,7 @@ export const NotificationsFeed: React.FC = () => {
<View style={styles.container}>
<FlashList
showsVerticalScrollIndicator={false}
data={[...mentionNotes, ...reactions, ...zaps].sort((a, b) => b.created_at - a.created_at)}
data={notifications.sort((a, b) => b.created_at - a.created_at)}
renderItem={renderItem}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
refreshing={refreshing}
@ -320,7 +265,7 @@ export const NotificationsFeed: React.FC = () => {
horizontal={false}
estimatedItemSize={100}
ListFooterComponent={
mentionNotes.length > 0 ? (
notifications.length > 0 ? (
<ActivityIndicator style={styles.loading} animating={true} />
) : (
<></>

View File

@ -8,24 +8,22 @@ import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityI
import { UserContext } from '../../Contexts/UserContext'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
import { Kind, nip19 } from 'nostr-tools'
import { getMentionNotes, getNotificationsIds } from '../../Functions/DatabaseFunctions/Notes'
import { AppContext } from '../../Contexts/AppContext'
import { StyleSheet } from 'react-native'
import RBSheet from 'react-native-raw-bottom-sheet'
import { useTranslation } from 'react-i18next'
import { navigate } from '../../lib/Navigation'
import {
getDirectMessagesCount,
getGroupedDirectMessages,
} from '../../Functions/DatabaseFunctions/DirectMessages'
import { getDirectMessagesCount } from '../../Functions/DatabaseFunctions/DirectMessages'
import GroupsFeed from './GroupsFeed'
import { getUserGroupMessagesCount } from '../../Functions/DatabaseFunctions/Groups'
import { getNotifications } from '../../Functions/DatabaseFunctions/Notifications'
import { getTaggedEventIds } from '../../Functions/RelayFunctions/Events'
export const HomePage: React.FC = () => {
const theme = useTheme()
const { t } = useTranslation('common')
const { language, setPushedTab } = React.useContext(AppContext)
const { privateKey, publicKey, mutedEvents } = React.useContext(UserContext)
const { privateKey, publicKey, mutedEvents, mutedUsers } = React.useContext(UserContext)
const { database, notificationSeenAt, clipboardNip21, setClipboardNip21, refreshBottomBarAt } =
useContext(AppContext)
const { relayPool, lastEventId } = useContext(RelayPoolContext)
@ -42,12 +40,15 @@ export const HomePage: React.FC = () => {
useEffect(() => {
if (publicKey && database && relayPool) {
getNotificationsIds(database, publicKey, notificationSeenAt).then((results) => {
const unmutedThreads = results.filter((id) => {
if (!id ?? id === '') return false
return !mutedEvents.includes(id)
})
setNewNotifications(unmutedThreads.length)
getNotifications(database, { since: notificationSeenAt }).then((results) => {
setNewNotifications(
results.filter((event) => {
const eTags = getTaggedEventIds(event)
return (
!mutedUsers.includes(event.pubkey) && !mutedEvents.some((id) => eTags.includes(id))
)
}).length,
)
})
getUserGroupMessagesCount(database, publicKey).then(setNewGroupMessages)
getDirectMessagesCount(database, publicKey).then(setNewdirectMessages)
@ -57,35 +58,23 @@ export const HomePage: React.FC = () => {
const subscribe: () => void = () => {
if (publicKey && database) {
getMentionNotes(database, publicKey, 1).then((mentionResults) => {
getGroupedDirectMessages(database, { limit: 1 }).then((directMessageResults) => {
relayPool?.subscribe('notification-icon', [
{
kinds: [Kind.ChannelMessage],
'#p': [publicKey],
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,
},
])
})
})
relayPool?.subscribe('notification-icon', [
{
kinds: [Kind.ChannelMessage],
'#p': [publicKey],
limit: 30,
},
{
kinds: [Kind.EncryptedDirectMessage],
'#p': [publicKey],
limit: 30,
},
{
kinds: [Kind.Text, Kind.Reaction, 9735],
'#p': [publicKey],
limit: 30,
},
])
}
}

View File

@ -52,7 +52,7 @@ export const NotePage: React.FC<NotePageProps> = ({ route }) => {
const loadGroup: () => void = async () => {
if (database) {
getGroup(database, route.params.noteId).then((result) => {
if (result) {
if (result.id) {
navigate('Group', {
groupId: result.id,
title: result.name ?? formatId(result.id),
@ -99,6 +99,7 @@ export const NotePage: React.FC<NotePageProps> = ({ route }) => {
}, [])
const subscribeNotes: (past?: boolean) => Promise<void> = async (past) => {
console.log(route.params)
if (database && route.params.noteId) {
relayPool?.subscribe(`notepage${route.params.noteId.substring(0, 8)}`, [
{

View File

@ -13,7 +13,7 @@
"dependencies": {
"@eva-design/eva": "^2.1.0",
"@react-native-clipboard/clipboard": "^1.11.2",
"@react-native-community/cli-platform-android": "^10.2.0",
"@react-native-community/cli-platform-android": "^11.1.0",
"@react-navigation/bottom-tabs": "^6.5.3",
"@react-navigation/drawer": "^6.5.7",
"@react-navigation/native": "^6.1.2",
@ -30,7 +30,7 @@
"cryptr": "^6.1.0",
"date-fns": "^2.29.3",
"events": "^3.3.0",
"i18next": "^22.4.13",
"i18next": "^22.4.14",
"lnurl-pay": "^2.2.0",
"lodash.debounce": "^4.0.8",
"nostr-tools": "^1.7.5",
@ -67,7 +67,7 @@
"uuid": "^9.0.0"
},
"devDependencies": {
"@babel/core": "^7.20.2",
"@babel/core": "^7.21.4",
"@babel/runtime": "^7.20.13",
"@react-native-community/eslint-config": "^3.2.0",
"@types/create-hash": "^1.2.2",
@ -84,7 +84,7 @@
"eslint": "^8.36.0",
"eslint-config-prettier": "^8.8.0",
"eslint-config-standard-with-typescript": "^34.0.1",
"eslint-import-resolver-typescript": "^3.5.3",
"eslint-import-resolver-typescript": "^3.5.4",
"eslint-plugin-i18next": "^6.0.0-8",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^15.6.0",

154
yarn.lock
View File

@ -49,12 +49,24 @@
dependencies:
"@babel/highlight" "^7.18.6"
"@babel/code-frame@^7.21.4":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39"
integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==
dependencies:
"@babel/highlight" "^7.18.6"
"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.5":
version "7.21.0"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.0.tgz#c241dc454e5b5917e40d37e525e2f4530c399298"
integrity sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==
"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.13.16", "@babel/core@^7.14.0", "@babel/core@^7.20.0", "@babel/core@^7.20.2":
"@babel/compat-data@^7.21.4":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.4.tgz#457ffe647c480dff59c2be092fc3acf71195c87f"
integrity sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==
"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.13.16", "@babel/core@^7.14.0", "@babel/core@^7.20.0":
version "7.21.3"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.3.tgz#cf1c877284a469da5d1ce1d1e53665253fae712e"
integrity sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw==
@ -75,6 +87,27 @@
json5 "^2.2.2"
semver "^6.3.0"
"@babel/core@^7.21.4":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.4.tgz#c6dc73242507b8e2a27fd13a9c1814f9fa34a659"
integrity sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==
dependencies:
"@ampproject/remapping" "^2.2.0"
"@babel/code-frame" "^7.21.4"
"@babel/generator" "^7.21.4"
"@babel/helper-compilation-targets" "^7.21.4"
"@babel/helper-module-transforms" "^7.21.2"
"@babel/helpers" "^7.21.0"
"@babel/parser" "^7.21.4"
"@babel/template" "^7.20.7"
"@babel/traverse" "^7.21.4"
"@babel/types" "^7.21.4"
convert-source-map "^1.7.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.2.2"
semver "^6.3.0"
"@babel/eslint-parser@^7.18.2":
version "7.21.3"
resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.21.3.tgz#d79e822050f2de65d7f368a076846e7184234af7"
@ -94,6 +127,16 @@
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
"@babel/generator@^7.21.4":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.4.tgz#64a94b7448989f421f919d5239ef553b37bb26bc"
integrity sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==
dependencies:
"@babel/types" "^7.21.4"
"@jridgewell/gen-mapping" "^0.3.2"
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
"@babel/helper-annotate-as-pure@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb"
@ -112,6 +155,17 @@
lru-cache "^5.1.1"
semver "^6.3.0"
"@babel/helper-compilation-targets@^7.21.4":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz#770cd1ce0889097ceacb99418ee6934ef0572656"
integrity sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==
dependencies:
"@babel/compat-data" "^7.21.4"
"@babel/helper-validator-option" "^7.21.0"
browserslist "^4.21.3"
lru-cache "^5.1.1"
semver "^6.3.0"
"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0":
version "7.21.0"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.0.tgz#64f49ecb0020532f19b1d014b03bccaa1ab85fb9"
@ -297,6 +351,11 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.3.tgz#1d285d67a19162ff9daa358d4cb41d50c06220b3"
integrity sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==
"@babel/parser@^7.21.4":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.4.tgz#94003fdfc520bbe2875d4ae557b43ddb6d880f17"
integrity sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==
"@babel/plugin-proposal-async-generator-functions@^7.0.0":
version "7.20.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz#bfb7276d2d573cb67ba379984a2334e262ba5326"
@ -787,6 +846,22 @@
debug "^4.1.0"
globals "^11.1.0"
"@babel/traverse@^7.21.4":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.4.tgz#a836aca7b116634e97a6ed99976236b3282c9d36"
integrity sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==
dependencies:
"@babel/code-frame" "^7.21.4"
"@babel/generator" "^7.21.4"
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-function-name" "^7.21.0"
"@babel/helper-hoist-variables" "^7.18.6"
"@babel/helper-split-export-declaration" "^7.18.6"
"@babel/parser" "^7.21.4"
"@babel/types" "^7.21.4"
debug "^4.1.0"
globals "^11.1.0"
"@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.2", "@babel/types@^7.21.3", "@babel/types@^7.3.0", "@babel/types@^7.3.3":
version "7.21.3"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.3.tgz#4865a5357ce40f64e3400b0f3b737dc6d4f64d05"
@ -796,6 +871,15 @@
"@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0"
"@babel/types@^7.21.4":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.4.tgz#2d5d6bb7908699b3b416409ffd3b5daa25b030d4"
integrity sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==
dependencies:
"@babel/helper-string-parser" "^7.19.4"
"@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0"
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@ -1302,6 +1386,17 @@
glob "^7.1.3"
logkitty "^0.7.1"
"@react-native-community/cli-platform-android@^11.1.0":
version "11.1.1"
resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-android/-/cli-platform-android-11.1.1.tgz#843381104e61417f2d337598c6f5ff484d1fa1b2"
integrity sha512-+qim4c00uomP0N2VxsE4Qk3ixYLeElsIuwN5DkrSRzKwGSJqzdvkChEof7mkW803OyCl6cEBT1yrRWpvtSZOHA==
dependencies:
"@react-native-community/cli-tools" "11.1.1"
chalk "^4.1.2"
execa "^5.0.0"
glob "^7.1.3"
logkitty "^0.7.1"
"@react-native-community/cli-platform-ios@10.2.0":
version "10.2.0"
resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-ios/-/cli-platform-ios-10.2.0.tgz#be21c0e3bbf17358d540cc23e5556bf679f6322e"
@ -1358,6 +1453,21 @@
serve-static "^1.13.1"
ws "^7.5.1"
"@react-native-community/cli-tools@11.1.1":
version "11.1.1"
resolved "https://registry.yarnpkg.com/@react-native-community/cli-tools/-/cli-tools-11.1.1.tgz#6ec1b6bf714cb7bdb360ca4a213e969e10b644f2"
integrity sha512-wkyzFxIncZCYfCHt1nwGE0pKspuP6yknNCF9nHxFXag0gPkTphpZWoV+uRVNRd+Fudx/qOtPiFAivGfw+8Pp2w==
dependencies:
appdirsjs "^1.2.4"
chalk "^4.1.2"
find-up "^5.0.0"
mime "^2.4.1"
node-fetch "^2.6.0"
open "^6.2.0"
ora "^5.4.1"
semver "^6.3.0"
shell-quote "^1.7.3"
"@react-native-community/cli-tools@^10.1.1":
version "10.1.1"
resolved "https://registry.yarnpkg.com/@react-native-community/cli-tools/-/cli-tools-10.1.1.tgz#fa66e509c0d3faa31f7bb87ed7d42ad63f368ddd"
@ -3369,7 +3479,7 @@ end-of-stream@^1.1.0:
dependencies:
once "^1.4.0"
enhanced-resolve@^5.10.0:
enhanced-resolve@^5.12.0:
version "5.12.0"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz#300e1c90228f5b570c4d35babf263f6da7155634"
integrity sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==
@ -3536,18 +3646,18 @@ eslint-import-resolver-node@^0.3.7:
is-core-module "^2.11.0"
resolve "^1.22.1"
eslint-import-resolver-typescript@^3.5.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.3.tgz#db5ed9e906651b7a59dd84870aaef0e78c663a05"
integrity sha512-njRcKYBc3isE42LaTcJNVANR3R99H9bAxBDMNDr2W7yq5gYPxbU3MkdhsQukxZ/Xg9C2vcyLlDsbKfRDg0QvCQ==
eslint-import-resolver-typescript@^3.5.4:
version "3.5.4"
resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.4.tgz#7370c326c3c08f0c1839c592d79d20b704de15d4"
integrity sha512-9xUpnedEmSfG57sN1UvWPiEhfJ8bPt0Wg2XysA7Mlc79iFGhmJtRUg9LxtkK81FhMUui0YuR2E8iUsVhePkh4A==
dependencies:
debug "^4.3.4"
enhanced-resolve "^5.10.0"
get-tsconfig "^4.2.0"
globby "^13.1.2"
is-core-module "^2.10.0"
enhanced-resolve "^5.12.0"
get-tsconfig "^4.5.0"
globby "^13.1.3"
is-core-module "^2.11.0"
is-glob "^4.0.3"
synckit "^0.8.4"
synckit "^0.8.5"
eslint-module-utils@^2.7.4:
version "2.7.4"
@ -4194,10 +4304,10 @@ get-symbol-description@^1.0.0:
call-bind "^1.0.2"
get-intrinsic "^1.1.1"
get-tsconfig@^4.2.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.4.0.tgz#64eee64596668a81b8fce18403f94f245ee0d4e5"
integrity sha512-0Gdjo/9+FzsYhXCEFueo2aY1z1tpXrxWZzP7k8ul9qt1U5o8rYJwTJYmaeHdrVosYIVYkOy2iwCJ9FdpocJhPQ==
get-tsconfig@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.5.0.tgz#6d52d1c7b299bd3ee9cd7638561653399ac77b0f"
integrity sha512-MjhiaIWCJ1sAU4pIQ5i5OfOuHHxVo1oYeNsWTON7jxYkod8pHocXeh+SSbmu5OZZZK73B6cbJ2XADzXehLyovQ==
get-value@^2.0.3, get-value@^2.0.6:
version "2.0.6"
@ -4266,7 +4376,7 @@ globby@^11.1.0:
merge2 "^1.4.1"
slash "^3.0.0"
globby@^13.1.2:
globby@^13.1.3:
version "13.1.3"
resolved "https://registry.yarnpkg.com/globby/-/globby-13.1.3.tgz#f62baf5720bcb2c1330c8d4ef222ee12318563ff"
integrity sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==
@ -4461,10 +4571,10 @@ human-signals@^2.1.0:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
i18next@^22.4.13:
version "22.4.13"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-22.4.13.tgz#02e291ab0056eab13b7d356fb454ff991923eaa0"
integrity sha512-GX7flMHRRqQA0I1yGLmaZ4Hwt1JfLqagk8QPDPZsqekbKtXsuIngSVWM/s3SLgNkrEXjA+0sMGNuOEkkmyqmWg==
i18next@^22.4.14:
version "22.4.14"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-22.4.14.tgz#18dd94e9adc2618497c7de101a206e1ca3a18727"
integrity sha512-VtLPtbdwGn0+DAeE00YkiKKXadkwg+rBUV+0v8v0ikEjwdiJ0gmYChVE4GIa9HXymY6wKapkL93vGT7xpq6aTw==
dependencies:
"@babel/runtime" "^7.20.6"
@ -4612,7 +4722,7 @@ is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7:
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
is-core-module@^2.10.0, is-core-module@^2.11.0, is-core-module@^2.9.0:
is-core-module@^2.11.0, is-core-module@^2.9.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144"
integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==
@ -7805,7 +7915,7 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
synckit@^0.8.4:
synckit@^0.8.5:
version "0.8.5"
resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.5.tgz#b7f4358f9bb559437f9f167eb6bc46b3c9818fa3"
integrity sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==