mirror of
https://github.com/KoalaSat/nostros.git
synced 2024-09-29 06:30:47 +00:00
Profile reactions 2
This commit is contained in:
parent
88f109a7e6
commit
d87968dbb6
@ -183,7 +183,7 @@ public class Event {
|
||||
String name = parts[0];
|
||||
String domain = parts[1];
|
||||
|
||||
if (!name.matches("^[a-zA-Z0-9-_]+$")) return false;
|
||||
if (name.length() == 0) return false;
|
||||
|
||||
try {
|
||||
String url = "https://" + domain + "/.well-known/nostr.json?name=" + name;
|
||||
@ -213,7 +213,7 @@ public class Event {
|
||||
String name = parts[0];
|
||||
String domain = parts[1];
|
||||
|
||||
if (!name.matches("^[a-zA-Z0-9-_]+$")) return "";
|
||||
if (name.length() == 0) return "";
|
||||
|
||||
try {
|
||||
String url = "https://" + domain + "/.well-known/lnurlp/" + name;
|
||||
|
@ -55,14 +55,4 @@ public class Relay {
|
||||
values.put("deleted_at", 0);
|
||||
database.replace("nostros_relays", null, values);
|
||||
}
|
||||
|
||||
public void delete(SQLiteDatabase database) {
|
||||
String whereClause = "url = ?";
|
||||
String[] whereArgs = new String[] {
|
||||
url
|
||||
};
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("deleted_at", System.currentTimeMillis() / 1000L);
|
||||
database.update ("nostros_relays", values, whereClause, whereArgs);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.nostros.modules;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.SQLException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
@ -172,18 +173,7 @@ public class DatabaseModule {
|
||||
database.execSQL("DROP INDEX nostros_notes_notifications_index;");
|
||||
} catch (SQLException e) { }
|
||||
try {
|
||||
database.execSQL("CREATE INDEX nostros_notes_relays_notes_index ON nostros_notes_relays(note_id, relay_url);");
|
||||
database.execSQL("CREATE INDEX nostros_notes_relays_users_index ON nostros_notes_relays(pubkey, relay_url);");
|
||||
|
||||
database.execSQL("CREATE INDEX nostros_direct_messages_feed_index ON nostros_direct_messages(pubkey, created_at); ");
|
||||
database.execSQL("CREATE INDEX nostros_direct_messages_notification_index ON nostros_direct_messages(pubkey, read); ");
|
||||
database.execSQL("CREATE INDEX nostros_direct_messages_conversation_index ON nostros_direct_messages(created_at, conversation_id); ");
|
||||
|
||||
database.execSQL("CREATE INDEX nostros_reactions_pubkey_index ON nostros_reactions(pubkey); ");
|
||||
database.execSQL("CREATE INDEX nostros_reactions_reacted_event_id_index ON nostros_reactions(reacted_event_id); ");
|
||||
database.execSQL("CREATE INDEX nostros_reactions_created_at_reacted_event_id_index ON nostros_reactions(created_at, reacted_event_id); ");
|
||||
|
||||
database.execSQL("CREATE INDEX nostros_users_contact_follower_index ON nostros_users(contact, follower); ");
|
||||
database.execSQL("CREATE INDEX nostros_users_names_index ON nostros_users(id, name); ");
|
||||
database.execSQL("CREATE INDEX nostros_users_contacts_index ON nostros_users(id, contact); ");
|
||||
database.execSQL("CREATE INDEX nostros_users_blocked_index ON nostros_users(id, blocked); ");
|
||||
@ -199,6 +189,24 @@ public class DatabaseModule {
|
||||
database.execSQL("CREATE INDEX nostros_group_messages_mentions_index ON nostros_group_messages(group_id, pubkey, created_at);");
|
||||
database.execSQL("CREATE INDEX nostros_group_messages_group_index ON nostros_group_messages(group_id, created_at);");
|
||||
database.execSQL("CREATE INDEX nostros_group_messages_feed_index ON nostros_group_messages(user_mentioned, read, group_id);");
|
||||
|
||||
database.execSQL("CREATE INDEX nostros_notes_relays_notes_index ON nostros_notes_relays(note_id, relay_url);");
|
||||
database.execSQL("CREATE INDEX nostros_notes_relays_users_index ON nostros_notes_relays(pubkey, relay_url);");
|
||||
|
||||
database.execSQL("CREATE INDEX nostros_direct_messages_feed_index ON nostros_direct_messages(pubkey, created_at); ");
|
||||
database.execSQL("CREATE INDEX nostros_direct_messages_notification_index ON nostros_direct_messages(pubkey, read); ");
|
||||
database.execSQL("CREATE INDEX nostros_direct_messages_conversation_index ON nostros_direct_messages(created_at, conversation_id); ");
|
||||
|
||||
// Previous
|
||||
database.execSQL("CREATE INDEX nostros_users_contact_follower_index ON nostros_users(contact, follower); ");
|
||||
database.execSQL("CREATE INDEX nostros_reactions_created_at_reacted_event_id_index ON nostros_reactions(created_at, reacted_event_id); ");
|
||||
database.execSQL("CREATE INDEX nostros_notes_pubkey_index ON nostros_notes(pubkey); ");
|
||||
database.execSQL("CREATE INDEX nostros_notes_main_event_id_index ON nostros_notes(main_event_id); ");
|
||||
database.execSQL("CREATE INDEX nostros_direct_messages_pubkey_index ON nostros_direct_messages(pubkey); ");
|
||||
database.execSQL("CREATE INDEX nostros_direct_messages_conversation_id_index ON nostros_direct_messages(conversation_id); ");
|
||||
database.execSQL("CREATE INDEX nostros_reactions_reacted_event_id_index ON nostros_reactions(reacted_event_id); ");
|
||||
database.execSQL("CREATE INDEX nostros_users_contact_index ON nostros_users(contact); ");
|
||||
database.execSQL("CREATE INDEX nostros_reactions_pubkey_index ON nostros_reactions(pubkey); ");
|
||||
} catch (SQLException e) { }
|
||||
}
|
||||
|
||||
@ -211,8 +219,14 @@ public class DatabaseModule {
|
||||
relay.save(database);
|
||||
}
|
||||
|
||||
public void deleteRelay(Relay relay) {
|
||||
relay.delete(database);
|
||||
public void deleteRelay(String relayUrl) {
|
||||
String whereClause = "url = ?";
|
||||
String[] whereArgs = new String[] {
|
||||
relayUrl
|
||||
};
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("deleted_at", System.currentTimeMillis() / 1000L);
|
||||
database.update ("nostros_relays", values, whereClause, whereArgs);
|
||||
}
|
||||
|
||||
public List<Relay> getRelays(ReactApplicationContext reactContext) {
|
||||
|
@ -55,10 +55,9 @@ public class RelayPoolModule extends ReactContextBaseJavaModule {
|
||||
if(url.equals(relay.url)){
|
||||
relay.disconnect();
|
||||
iterator.remove();
|
||||
database.deleteRelay(relay);
|
||||
}
|
||||
}
|
||||
|
||||
database.deleteRelay(url);
|
||||
callback.invoke();
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ export const LnPayment: React.FC<LnPaymentProps> = ({ open, setOpen, note, user
|
||||
setMonto('')
|
||||
if (open) {
|
||||
if (database && note?.id) {
|
||||
getZaps(database, note?.id).then((results) => {
|
||||
getZaps(database, { eventId: note?.id }).then((results) => {
|
||||
relayPool?.subscribe('zappers-meta', [
|
||||
{
|
||||
kinds: [Kind.Metadata],
|
||||
@ -68,7 +68,7 @@ export const LnPayment: React.FC<LnPaymentProps> = ({ open, setOpen, note, user
|
||||
|
||||
useEffect(() => {
|
||||
if (database && note?.id) {
|
||||
getZaps(database, note?.id).then((results) => {
|
||||
getZaps(database, { eventId: note?.id }).then((results) => {
|
||||
setZaps(results)
|
||||
setZapsUpdated(getUnixTime(new Date()))
|
||||
})
|
||||
|
@ -30,7 +30,7 @@ export const NoteActions: React.FC<NoteActionsProps> = ({ bottomSheetRef }) => {
|
||||
|
||||
const loadNote: () => void = () => {
|
||||
if (database && displayNoteDrawer) {
|
||||
getNotes(database, { filters: { id: displayNoteDrawer } }).then((results) => {
|
||||
getNotes(database, { filters: { id: [displayNoteDrawer] } }).then((results) => {
|
||||
if (results.length > 0) {
|
||||
setNote(results[0])
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
Note,
|
||||
NoteRelay,
|
||||
} from '../../Functions/DatabaseFunctions/Notes'
|
||||
import { StyleSheet, TouchableNativeFeedback, View } from 'react-native'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import { t } from 'i18next'
|
||||
@ -134,7 +134,7 @@ export const NoteCard: React.FC<NoteCardProps> = ({
|
||||
})
|
||||
}
|
||||
if (showRepostPreview && note.repost_id) {
|
||||
getNotes(database, { filters: { id: note.repost_id } }).then((events) => {
|
||||
getNotes(database, { filters: { id: [note.repost_id] } }).then((events) => {
|
||||
if (events.length > 0) {
|
||||
setRepost(events[0])
|
||||
}
|
||||
@ -482,16 +482,15 @@ export const NoteCard: React.FC<NoteCardProps> = ({
|
||||
{relayColouring &&
|
||||
showRelayColors &&
|
||||
relays.map((relay, index) => (
|
||||
<TouchableNativeFeedback key={relay.relay_url}>
|
||||
<View
|
||||
style={[
|
||||
styles.relay,
|
||||
{ borderBottomColor: relayToColor(relay.relay_url) },
|
||||
index === 0 ? { borderBottomLeftRadius: 50 } : {},
|
||||
index === relays.length - 1 ? { borderBottomRightRadius: 50 } : {},
|
||||
]}
|
||||
/>
|
||||
</TouchableNativeFeedback>
|
||||
<View
|
||||
key={relay.relay_url}
|
||||
style={[
|
||||
styles.relay,
|
||||
{ borderBottomColor: relayToColor(relay.relay_url) },
|
||||
index === 0 ? { borderBottomLeftRadius: 50 } : {},
|
||||
index === relays.length - 1 ? { borderBottomRightRadius: 50 } : {},
|
||||
]}
|
||||
/>
|
||||
))}
|
||||
</Card.Content>
|
||||
</Card>
|
||||
|
64
frontend/Components/Tabs/index.tsx
Normal file
64
frontend/Components/Tabs/index.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ScrollView, StyleSheet, View } from 'react-native'
|
||||
import { Text, TouchableRipple, useTheme } from 'react-native-paper'
|
||||
|
||||
interface TabsProps {
|
||||
tabs: string[]
|
||||
activeTab: string
|
||||
setActiveTab: (activeTab: string) => void
|
||||
}
|
||||
|
||||
export const Tabs: React.FC<TabsProps> = ({ tabs, activeTab, setActiveTab }) => {
|
||||
const theme = useTheme()
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
return (
|
||||
<View style={styles.tabsNavigator}>
|
||||
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
||||
{tabs.map((tabKey) => (
|
||||
<View
|
||||
key={tabKey}
|
||||
style={[
|
||||
styles.tab,
|
||||
{
|
||||
borderBottomColor: tabKey === activeTab ? theme.colors.primary : theme.colors.border,
|
||||
borderBottomWidth: tabKey === activeTab ? 3 : 1,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<TouchableRipple
|
||||
style={styles.textWrapper}
|
||||
onPress={() => {
|
||||
setActiveTab(tabKey)
|
||||
}}
|
||||
>
|
||||
<Text style={styles.tabText}>{t(`tabs.${tabKey}`)}</Text>
|
||||
</TouchableRipple>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
tabText: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
tabsNavigator: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
height: 48,
|
||||
},
|
||||
tab: {
|
||||
width: 160,
|
||||
},
|
||||
textWrapper: {
|
||||
justifyContent: 'center',
|
||||
height: '100%',
|
||||
textAlign: 'center',
|
||||
},
|
||||
})
|
||||
|
||||
export default Tabs
|
@ -36,11 +36,11 @@ export const getMainNotes: (
|
||||
db: QuickSQLiteConnection,
|
||||
pubKey: string,
|
||||
limit: number,
|
||||
contants: boolean,
|
||||
filters?: {
|
||||
contants?: boolean,
|
||||
until?: number
|
||||
},
|
||||
) => Promise<Note[]> = async (db, pubKey, limit, contants, filters) => {
|
||||
) => Promise<Note[]> = async (db, pubKey, limit, filters) => {
|
||||
let notesQuery = `
|
||||
SELECT
|
||||
nostros_notes.*, nostros_users.zap_pubkey, nostros_users.nip05, nostros_users.blocked, nostros_users.valid_nip05,
|
||||
@ -51,8 +51,11 @@ export const getMainNotes: (
|
||||
WHERE
|
||||
`
|
||||
|
||||
if (contants)
|
||||
if (filters?.contants) {
|
||||
notesQuery += `(nostros_users.contact = 1 OR nostros_notes.pubkey = '${pubKey}') AND `
|
||||
} else {
|
||||
notesQuery += `nostros_notes.pubkey = '${pubKey}' AND `
|
||||
}
|
||||
|
||||
if (filters?.until) notesQuery += `nostros_notes.created_at <= ${filters?.until} AND `
|
||||
|
||||
@ -68,6 +71,40 @@ export const getMainNotes: (
|
||||
|
||||
return notes
|
||||
}
|
||||
export const getReplyNotes: (
|
||||
db: QuickSQLiteConnection,
|
||||
pubKey: string,
|
||||
limit: number,
|
||||
filters?: {
|
||||
until?: number
|
||||
},
|
||||
) => Promise<Note[]> = async (db, pubKey, limit, filters) => {
|
||||
let notesQuery = `
|
||||
SELECT
|
||||
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
|
||||
nostros_notes.pubkey = '${pubKey}' AND
|
||||
`
|
||||
|
||||
if (filters?.until) notesQuery += `nostros_notes.created_at <= ${filters?.until} AND `
|
||||
|
||||
notesQuery += `
|
||||
nostros_notes.main_event_id IS NOT NULL AND
|
||||
nostros_notes.repost_id IS NULL
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ${limit}
|
||||
`
|
||||
|
||||
const resultSet = await db.execute(notesQuery)
|
||||
const items: object[] = getItems(resultSet)
|
||||
const notes: Note[] = items.map((object) => databaseToEntity(object))
|
||||
|
||||
return notes
|
||||
}
|
||||
|
||||
export const getMainNotesCount: (
|
||||
db: QuickSQLiteConnection,
|
||||
@ -299,7 +336,7 @@ export const getRawUserNotes: (
|
||||
export const getNotes: (
|
||||
db: QuickSQLiteConnection,
|
||||
options: {
|
||||
filters?: Record<string, string>
|
||||
filters?: Record<string, string[]>
|
||||
limit?: number
|
||||
contacts?: boolean
|
||||
includeIds?: string[]
|
||||
@ -319,7 +356,7 @@ export const getNotes: (
|
||||
if (Object.keys(filters).length > 0) {
|
||||
notesQuery += 'WHERE '
|
||||
keys.forEach((column, index) => {
|
||||
notesQuery += `nostros_notes.${column} = '${filters[column]}' `
|
||||
notesQuery += `nostros_notes.${column} IN ('${filters[column].join("', '")}') `
|
||||
if (index < keys.length - 1) notesQuery += 'AND '
|
||||
})
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { getUnixTime } from 'date-fns'
|
||||
import { QuickSQLiteConnection } from 'react-native-quick-sqlite'
|
||||
import { getItems } from '..'
|
||||
import { Event } from '../../../lib/nostr/Events'
|
||||
@ -38,11 +39,58 @@ export const getZapsAmount: (
|
||||
return item['SUM(amount)'] ?? 0
|
||||
}
|
||||
|
||||
export const getZaps: (db: QuickSQLiteConnection, eventId: string) => Promise<Zap[]> = async (
|
||||
export const getMostZapedNotes: (
|
||||
db: QuickSQLiteConnection,
|
||||
publicKey: string,
|
||||
limit: number,
|
||||
since: number
|
||||
) => Promise<Zap[]> = async (db, publicKey, limit, since) => {
|
||||
const zapsQuery = `
|
||||
SELECT
|
||||
SUM(amount) as total, *
|
||||
FROM
|
||||
nostros_zaps
|
||||
WHERE zapped_user_id = '${publicKey}'
|
||||
AND created_at > ${since}
|
||||
GROUP BY zapped_event_id
|
||||
ORDER BY total DESC
|
||||
LIMIT ${limit}
|
||||
`
|
||||
const resultSet = await db.execute(zapsQuery)
|
||||
const items: object[] = getItems(resultSet)
|
||||
const zaps: Zap[] = items.map((object) => databaseToEntity(object))
|
||||
|
||||
return zaps
|
||||
}
|
||||
|
||||
export const getMostZapedNotesContacts: (
|
||||
db: QuickSQLiteConnection,
|
||||
since: number
|
||||
) => Promise<Zap[]> = async (db, since) => {
|
||||
const zapsQuery = `
|
||||
SELECT
|
||||
SUM(amount) as total, nostros_zaps.*
|
||||
FROM
|
||||
nostros_zaps
|
||||
LEFT JOIN
|
||||
nostros_users ON nostros_users.id = nostros_zaps.zapped_user_id
|
||||
WHERE nostros_zaps.created_at > ${since}
|
||||
AND nostros_users.contact = 1
|
||||
GROUP BY nostros_zaps.zapped_event_id
|
||||
ORDER BY total DESC
|
||||
`
|
||||
const resultSet = await db.execute(zapsQuery)
|
||||
const items: object[] = getItems(resultSet)
|
||||
const zaps: Zap[] = items.map((object) => databaseToEntity(object))
|
||||
|
||||
return zaps
|
||||
}
|
||||
|
||||
export const getZaps: (db: QuickSQLiteConnection, filters: { eventId?: string, zapperId?: string, limit?: number }) => Promise<Zap[]> = async (
|
||||
db,
|
||||
eventId,
|
||||
filters,
|
||||
) => {
|
||||
const groupsQuery = `
|
||||
let 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
|
||||
@ -50,11 +98,28 @@ export const getZaps: (db: QuickSQLiteConnection, eventId: string) => Promise<Za
|
||||
nostros_zaps
|
||||
LEFT JOIN
|
||||
nostros_users ON nostros_users.id = nostros_zaps.zapper_user_id
|
||||
WHERE zapped_event_id = "${eventId}"
|
||||
`
|
||||
|
||||
if (filters.eventId) {
|
||||
groupsQuery += `
|
||||
WHERE zapped_event_id = "${filters.eventId}"
|
||||
`
|
||||
}
|
||||
|
||||
if (filters.zapperId) {
|
||||
groupsQuery += `
|
||||
WHERE zapped_user_id = "${filters.zapperId}"
|
||||
`
|
||||
}
|
||||
|
||||
if (filters.limit) {
|
||||
groupsQuery += `
|
||||
LIMIT ${filters.limit}
|
||||
`
|
||||
}
|
||||
const resultSet = await db.execute(groupsQuery)
|
||||
const items: object[] = getItems(resultSet)
|
||||
const notes: Zap[] = items.map((object) => databaseToEntity(object))
|
||||
const zaps: Zap[] = items.map((object) => databaseToEntity(object))
|
||||
|
||||
return notes
|
||||
return zaps
|
||||
}
|
||||
|
@ -199,15 +199,20 @@
|
||||
"emptyTitle": "Du hast noch nichts repostet",
|
||||
"emptyDescription": "Hier sind Notizen, du du repostet hast"
|
||||
},
|
||||
"homeFeed": {
|
||||
"emptyTitle": "Du folgst niemandem",
|
||||
"emptyDescription": "Folge anderen um hier etwas zu sehen",
|
||||
"emptyButton": "Kontakte",
|
||||
"tabs": {
|
||||
"globalFeed": "Globaler Feed",
|
||||
"myFeed": "Mein Feed",
|
||||
"reactions": "Reaktionen",
|
||||
"mentions": "Erwähnungen",
|
||||
"reposts": "Reposts",
|
||||
"zaps": "Most zapped",
|
||||
"notes": "Notes",
|
||||
"replies": "Replies"
|
||||
},
|
||||
"homeFeed": {
|
||||
"emptyTitle": "Du folgst niemandem",
|
||||
"emptyDescription": "Folge anderen um hier etwas zu sehen",
|
||||
"emptyButton": "Kontakte",
|
||||
"newMessage": "{{newNotesCount}} neue Nachricht. Zum Aktualisieren tippen.",
|
||||
"newMessages": "{{newNotesCount}} neue Nachrichten. Zum Aktualisieren tippen."
|
||||
},
|
||||
|
@ -199,15 +199,20 @@
|
||||
"emptyTitle": "You still didn't reposted",
|
||||
"emptyDescription": "See here the notes you reposted."
|
||||
},
|
||||
"homeFeed": {
|
||||
"emptyTitle": "You are not following anyone",
|
||||
"emptyDescription": "Follow other profiles to see content.",
|
||||
"emptyButton": "Go to contacts",
|
||||
"tabs": {
|
||||
"globalFeed": "Global feed",
|
||||
"myFeed": "My feed",
|
||||
"reactions": "Reactions",
|
||||
"mentions": "Mentions",
|
||||
"reposts": "Reposts",
|
||||
"zaps": "Most zapped",
|
||||
"notes": "Notes",
|
||||
"replies": "Replies"
|
||||
},
|
||||
"homeFeed": {
|
||||
"emptyTitle": "You are not following anyone",
|
||||
"emptyDescription": "Follow other profiles to see content.",
|
||||
"emptyButton": "Go to contacts",
|
||||
"newMessage": "{{newNotesCount}} new note. Pull to refresh.",
|
||||
"newMessages": "{{newNotesCount}} new notes. Pull to refresh."
|
||||
},
|
||||
|
@ -212,14 +212,22 @@
|
||||
"nips": "NIPs",
|
||||
"version": "Version"
|
||||
},
|
||||
"tabs": {
|
||||
"myFeed": "Mi feed",
|
||||
"globalFeed": "Feed global",
|
||||
"reactions": "Reacciones",
|
||||
"mentions": "Menciones",
|
||||
"reposts": "Reposteados",
|
||||
"zaps": "Más zappeados",
|
||||
"notes": "Notas",
|
||||
"replies": "Respuestas"
|
||||
},
|
||||
"homeFeed": {
|
||||
"emptyTitle": "No sigues a nadie",
|
||||
"emptyDescription": "Sigue otros perfiles para ver contenido aquí.",
|
||||
"emptyButton": "Ir a contactos",
|
||||
"globalFeed": "Feed global",
|
||||
"newMessage": "{{newNotesCount}} nota nueva. Desliza para recargar.",
|
||||
"newMessages": "{{newNotesCount}} notas nuevas. Desliza para refrescar.",
|
||||
"myFeed": "Mi feed"
|
||||
"newMessages": "{{newNotesCount}} notas nuevas. Desliza para refrescar."
|
||||
},
|
||||
"relayCard": {
|
||||
"pushDone": "Completado",
|
||||
|
@ -218,14 +218,21 @@
|
||||
"nips": "NIPs",
|
||||
"version": "Version"
|
||||
},
|
||||
"tabs": {
|
||||
"myFeed": "Mon flux",
|
||||
"globalFeed": "Flux global",
|
||||
"reactions": "Reactions",
|
||||
"mentions": "Mentions",
|
||||
"reposts": "Reposts",
|
||||
"zaps": "Most zapped",
|
||||
"notes": "Notes",
|
||||
"replies": "Replies"
|
||||
},
|
||||
"homeFeed": {
|
||||
"emptyTitle": "Vous ne suivez personne",
|
||||
"emptyDescription": "Suivez les autres profils pour voir le contenu ici.",
|
||||
"emptyButton": "Accéder aux contacts",
|
||||
"globalFeed": "Flux global",
|
||||
"newMessage": "{{newNotesCount}} nouvelle note. Balayez pour recharger.",
|
||||
"newMessages": "{{newNotesCount}} nouvelles notes. Balayez pour recharger.",
|
||||
"myFeed": "Mon flux"
|
||||
"newMessages": "{{newNotesCount}} nouvelles notes. Balayez pour recharger."
|
||||
},
|
||||
"relayCard": {
|
||||
"pushDone": "Completed",
|
||||
|
@ -212,14 +212,22 @@
|
||||
"nips": "NIPs",
|
||||
"version": "Version"
|
||||
},
|
||||
"tabs": {
|
||||
"myFeed": "My feed",
|
||||
"globalFeed": "Global feed",
|
||||
"reactions": "Reactions",
|
||||
"mentions": "Mentions",
|
||||
"reposts": "Reposts",
|
||||
"zaps": "Most zapped",
|
||||
"notes": "Notes",
|
||||
"replies": "Replies"
|
||||
},
|
||||
"homeFeed": {
|
||||
"emptyTitle": "Вы ни на кого не подписаны",
|
||||
"emptyDescription": "Подпишитесь на другие профили, что бы увидеть, чем они делятся",
|
||||
"emptyButton": "Посмотреть контакты",
|
||||
"globalFeed": "Global feed",
|
||||
"newMessage": "{{newNotesCount}} nota nueva. Desliza para recargar.",
|
||||
"newMessages": "{{newNotesCount}} notas nuevas. Desliza para refrescar.",
|
||||
"myFeed": "My feed"
|
||||
"newMessages": "{{newNotesCount}} notas nuevas. Desliza para refrescar."
|
||||
},
|
||||
"relayCard": {
|
||||
"pushDone": "Completed",
|
||||
|
@ -218,15 +218,20 @@
|
||||
"emptyTitle": "您还没有转发任何 Notes",
|
||||
"emptyDescription": "查看您您转发过的 Notes"
|
||||
},
|
||||
"homeFeed": {
|
||||
"emptyTitle": "您还没有关注任何人",
|
||||
"emptyDescription": "关注其他人以查看内容",
|
||||
"emptyButton": "查看联系人",
|
||||
"tabs": {
|
||||
"globalFeed": "发现",
|
||||
"myFeed": "关注中",
|
||||
"reactions": "回应",
|
||||
"mentions": "提及",
|
||||
"reposts": "转发",
|
||||
"zaps": "Most zapped",
|
||||
"notes": "Notes",
|
||||
"replies": "Replies"
|
||||
},
|
||||
"homeFeed": {
|
||||
"emptyTitle": "您还没有关注任何人",
|
||||
"emptyDescription": "关注其他人以查看内容",
|
||||
"emptyButton": "查看联系人",
|
||||
"newMessage": "有{{newNotesCount}}条新的 Notes,下拉刷新",
|
||||
"newMessages": "有{{newNotesCount}}条新的 Notes,下拉刷新"
|
||||
},
|
||||
|
@ -364,7 +364,6 @@ export const ConversationPage: React.FC<ConversationPageProps> = ({ route }) =>
|
||||
renderItem={renderDirectMessageItem}
|
||||
horizontal={false}
|
||||
ref={scrollViewRef}
|
||||
estimatedItemSize={100}
|
||||
onScroll={onScroll}
|
||||
/>
|
||||
{reply ? (
|
||||
|
@ -120,6 +120,10 @@ export const GroupsFeed: React.FC = () => {
|
||||
kinds: [Kind.ChannelMetadata],
|
||||
'#e': results.map((group) => group.id ?? ''),
|
||||
},
|
||||
{
|
||||
kinds: [Kind.ChannelMessage],
|
||||
'#e': results.map((group) => group.id ?? '')
|
||||
},
|
||||
])
|
||||
}
|
||||
})
|
||||
|
@ -7,47 +7,45 @@ import {
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import { getMainNotes, getMainNotesCount, Note } from '../../Functions/DatabaseFunctions/Notes'
|
||||
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
|
||||
import { UserContext } from '../../Contexts/UserContext'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { AppContext } from '../../../Contexts/AppContext'
|
||||
import { getMainNotes, getMainNotesCount, Note } from '../../../Functions/DatabaseFunctions/Notes'
|
||||
import { handleInfinityScroll } from '../../../Functions/NativeFunctions'
|
||||
import { UserContext } from '../../../Contexts/UserContext'
|
||||
import { RelayPoolContext } from '../../../Contexts/RelayPoolContext'
|
||||
import { Kind } from 'nostr-tools'
|
||||
import { RelayFilters } from '../../lib/nostr/RelayPool/intex'
|
||||
import { RelayFilters } from '../../../lib/nostr/RelayPool/intex'
|
||||
import { Chip, Button, Text } from 'react-native-paper'
|
||||
import NoteCard from '../../Components/NoteCard'
|
||||
import NoteCard from '../../../Components/NoteCard'
|
||||
import { useTheme } from '@react-navigation/native'
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
|
||||
import { t } from 'i18next'
|
||||
import { FlashList, ListRenderItem } from '@shopify/flash-list'
|
||||
import { getUnixTime } from 'date-fns'
|
||||
|
||||
interface GlobalFeedProps {
|
||||
navigation: any
|
||||
updateLastLoad: () => void
|
||||
lastLoadAt: number
|
||||
pageSize: number
|
||||
setPageSize: (pageSize: number) => void
|
||||
}
|
||||
|
||||
export const GlobalFeed: React.FC<GlobalFeedProps> = ({ navigation }) => {
|
||||
export const GlobalFeed: React.FC<GlobalFeedProps> = ({
|
||||
navigation,
|
||||
updateLastLoad,
|
||||
lastLoadAt,
|
||||
pageSize,
|
||||
setPageSize,
|
||||
}) => {
|
||||
const initialPageSize = 10
|
||||
const theme = useTheme()
|
||||
const { database, showPublicImages, pushedTab } = useContext(AppContext)
|
||||
const { publicKey } = useContext(UserContext)
|
||||
const { lastEventId, relayPool, lastConfirmationtId } = useContext(RelayPoolContext)
|
||||
const initialPageSize = 10
|
||||
const [notes, setNotes] = useState<Note[]>([])
|
||||
const [lastLoadAt, setLastLoadAt] = useState<number>(0)
|
||||
const [newNotesCount, setNewNotesCount] = useState<number>(0)
|
||||
const [pageSize, setPageSize] = useState<number>(initialPageSize)
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
const flashListRef = React.useRef<FlashList<Note>>(null)
|
||||
|
||||
const unsubscribe: () => void = () => {
|
||||
relayPool?.unsubscribe(['homepage-global-main', 'homepage-global-meta-repost'])
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
unsubscribe()
|
||||
subscribeNotes()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (pushedTab) {
|
||||
flashListRef.current?.scrollToIndex({ animated: true, index: 0 })
|
||||
@ -58,42 +56,15 @@ export const GlobalFeed: React.FC<GlobalFeedProps> = ({ navigation }) => {
|
||||
if (relayPool && publicKey) {
|
||||
loadNotes()
|
||||
}
|
||||
}, [lastEventId, lastConfirmationtId, lastLoadAt])
|
||||
|
||||
useEffect(() => {
|
||||
if (pageSize > initialPageSize) {
|
||||
subscribeNotes(true)
|
||||
}
|
||||
}, [pageSize])
|
||||
|
||||
const updateLastLoad: () => void = () => {
|
||||
setLastLoadAt(getUnixTime(new Date()) - 5)
|
||||
}
|
||||
}, [lastEventId, lastConfirmationtId, lastLoadAt, relayPool, publicKey])
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
setRefreshing(true)
|
||||
updateLastLoad()
|
||||
setNewNotesCount(0)
|
||||
unsubscribe()
|
||||
subscribeNotes()
|
||||
updateLastLoad()
|
||||
flashListRef.current?.scrollToIndex({ animated: true, index: 0 })
|
||||
}, [])
|
||||
|
||||
const subscribeNotes: (past?: boolean) => void = async (past) => {
|
||||
if (!database || !publicKey) return
|
||||
|
||||
const message: RelayFilters = {
|
||||
kinds: [Kind.Text, Kind.RecommendRelay],
|
||||
limit: pageSize,
|
||||
}
|
||||
|
||||
if (past) message.until = lastLoadAt
|
||||
|
||||
relayPool?.subscribe('homepage-global-main', [message])
|
||||
setRefreshing(false)
|
||||
updateLastLoad()
|
||||
}
|
||||
|
||||
const onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void = (event) => {
|
||||
if (handleInfinityScroll(event)) {
|
||||
setPageSize(pageSize + initialPageSize)
|
||||
@ -105,7 +76,7 @@ export const GlobalFeed: React.FC<GlobalFeedProps> = ({ navigation }) => {
|
||||
if (lastLoadAt > 0) {
|
||||
getMainNotesCount(database, lastLoadAt).then(setNewNotesCount)
|
||||
}
|
||||
getMainNotes(database, publicKey, pageSize, false, {
|
||||
getMainNotes(database, publicKey, pageSize, {
|
||||
until: lastLoadAt,
|
||||
}).then((results) => {
|
||||
setRefreshing(false)
|
||||
@ -121,7 +92,7 @@ export const GlobalFeed: React.FC<GlobalFeedProps> = ({ navigation }) => {
|
||||
ids: repostIds,
|
||||
},
|
||||
]
|
||||
relayPool?.subscribe('homepage-global-meta-repost', message)
|
||||
relayPool?.subscribe('homepage-global-reposts', message)
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -168,7 +139,6 @@ export const GlobalFeed: React.FC<GlobalFeedProps> = ({ navigation }) => {
|
||||
<View>
|
||||
<View style={styles.list}>
|
||||
<FlashList
|
||||
estimatedItemSize={200}
|
||||
showsVerticalScrollIndicator={false}
|
||||
data={notes}
|
||||
renderItem={renderItem}
|
@ -1,5 +1,4 @@
|
||||
import React, { useCallback, useContext, useState, useEffect } from 'react'
|
||||
import { getUsers, User } from '../../Functions/DatabaseFunctions/Users'
|
||||
import {
|
||||
NativeScrollEvent,
|
||||
NativeSyntheticEvent,
|
||||
@ -7,15 +6,15 @@ import {
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import { getMainNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
|
||||
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
|
||||
import { UserContext } from '../../Contexts/UserContext'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { AppContext } from '../../../Contexts/AppContext'
|
||||
import { getMainNotes, Note } from '../../../Functions/DatabaseFunctions/Notes'
|
||||
import { handleInfinityScroll } from '../../../Functions/NativeFunctions'
|
||||
import { UserContext } from '../../../Contexts/UserContext'
|
||||
import { RelayPoolContext } from '../../../Contexts/RelayPoolContext'
|
||||
import { Kind } from 'nostr-tools'
|
||||
import { RelayFilters } from '../../lib/nostr/RelayPool/intex'
|
||||
import { RelayFilters } from '../../../lib/nostr/RelayPool/intex'
|
||||
import { ActivityIndicator, Button, Text } from 'react-native-paper'
|
||||
import NoteCard from '../../Components/NoteCard'
|
||||
import NoteCard from '../../../Components/NoteCard'
|
||||
import { useTheme } from '@react-navigation/native'
|
||||
import { FlashList, ListRenderItem } from '@shopify/flash-list'
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
|
||||
@ -23,9 +22,12 @@ import { useTranslation } from 'react-i18next'
|
||||
|
||||
interface MyFeedProps {
|
||||
navigation: any
|
||||
updateLastLoad: () => void
|
||||
pageSize: number
|
||||
setPageSize: (pageSize: number) => void
|
||||
}
|
||||
|
||||
export const MyFeed: React.FC<MyFeedProps> = ({ navigation }) => {
|
||||
export const MyFeed: React.FC<MyFeedProps> = ({ navigation, updateLastLoad, pageSize, setPageSize }) => {
|
||||
const theme = useTheme()
|
||||
const { t } = useTranslation('common')
|
||||
const { database, pushedTab } = useContext(AppContext)
|
||||
@ -33,7 +35,6 @@ export const MyFeed: React.FC<MyFeedProps> = ({ navigation }) => {
|
||||
const { lastEventId, relayPool, lastConfirmationtId } = useContext(RelayPoolContext)
|
||||
const initialPageSize = 10
|
||||
const [notes, setNotes] = useState<Note[]>([])
|
||||
const [pageSize, setPageSize] = useState<number>(initialPageSize)
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
const flashListRef = React.useRef<FlashList<Note>>(null)
|
||||
|
||||
@ -43,26 +44,15 @@ export const MyFeed: React.FC<MyFeedProps> = ({ navigation }) => {
|
||||
}
|
||||
}, [pushedTab])
|
||||
|
||||
useEffect(() => {
|
||||
subscribeNotes()
|
||||
loadNotes()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (relayPool && publicKey) {
|
||||
loadNotes()
|
||||
}
|
||||
}, [lastEventId, lastConfirmationtId])
|
||||
|
||||
useEffect(() => {
|
||||
if (pageSize > initialPageSize) {
|
||||
subscribeNotes(true)
|
||||
}
|
||||
}, [pageSize])
|
||||
}, [lastEventId, lastConfirmationtId, relayPool, publicKey])
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
setRefreshing(true)
|
||||
subscribeNotes()
|
||||
updateLastLoad()
|
||||
}, [])
|
||||
|
||||
const onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void = (event) => {
|
||||
@ -71,24 +61,11 @@ export const MyFeed: React.FC<MyFeedProps> = ({ navigation }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const subscribeNotes: (past?: boolean) => void = async (past) => {
|
||||
if (!database || !publicKey) return
|
||||
const users: User[] = await getUsers(database, { contacts: true, order: 'created_at DESC' })
|
||||
const authors: string[] = [...users.map((user) => user.id), publicKey]
|
||||
|
||||
const message: RelayFilters = {
|
||||
kinds: [Kind.Text, Kind.RecommendRelay],
|
||||
authors,
|
||||
limit: pageSize,
|
||||
}
|
||||
relayPool?.subscribe('homepage-contacts-main', [message])
|
||||
setRefreshing(false)
|
||||
}
|
||||
|
||||
const loadNotes: () => void = async () => {
|
||||
if (database && publicKey) {
|
||||
getMainNotes(database, publicKey, pageSize, true).then(async (notes) => {
|
||||
getMainNotes(database, publicKey, pageSize, { contants: true }).then(async (notes) => {
|
||||
setNotes(notes)
|
||||
setRefreshing(false)
|
||||
if (notes.length > 0) {
|
||||
const noteIds = notes.map((note) => note.id ?? '')
|
||||
const authors = notes.map((note) => note.pubkey ?? '')
|
||||
@ -110,7 +87,7 @@ export const MyFeed: React.FC<MyFeedProps> = ({ navigation }) => {
|
||||
}
|
||||
relayPool?.subscribe('homepage-contacts-reactions',reactionFilters )
|
||||
if (repostIds.length > 0) {
|
||||
relayPool?.subscribe('homepage-contacts-repost', [
|
||||
relayPool?.subscribe('homepage-contacts-reposts', [
|
||||
{
|
||||
kinds: [Kind.Text],
|
||||
ids: repostIds,
|
||||
@ -156,7 +133,6 @@ export const MyFeed: React.FC<MyFeedProps> = ({ navigation }) => {
|
||||
return (
|
||||
<View style={styles.list}>
|
||||
<FlashList
|
||||
estimatedItemSize={200}
|
||||
showsVerticalScrollIndicator={false}
|
||||
data={notes}
|
||||
renderItem={renderItem}
|
204
frontend/Pages/HomeFeed/ZapsFeed/index.tsx
Normal file
204
frontend/Pages/HomeFeed/ZapsFeed/index.tsx
Normal file
@ -0,0 +1,204 @@
|
||||
import React, { useCallback, useContext, useState, useEffect } from 'react'
|
||||
import {
|
||||
NativeScrollEvent,
|
||||
NativeSyntheticEvent,
|
||||
RefreshControl,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import { AppContext } from '../../../Contexts/AppContext'
|
||||
import { getNotes, Note } from '../../../Functions/DatabaseFunctions/Notes'
|
||||
import { handleInfinityScroll } from '../../../Functions/NativeFunctions'
|
||||
import { UserContext } from '../../../Contexts/UserContext'
|
||||
import { RelayPoolContext } from '../../../Contexts/RelayPoolContext'
|
||||
import { Kind } from 'nostr-tools'
|
||||
import { RelayFilters } from '../../../lib/nostr/RelayPool/intex'
|
||||
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 MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getMostZapedNotesContacts } from '../../../Functions/DatabaseFunctions/Zaps'
|
||||
import { getUnixTime } from 'date-fns'
|
||||
|
||||
interface ZapsFeedProps {
|
||||
navigation: any
|
||||
updateLastLoad: () => void
|
||||
pageSize: number
|
||||
setPageSize: (pageSize: number) => void
|
||||
}
|
||||
|
||||
export const ZapsFeed: React.FC<ZapsFeedProps> = ({
|
||||
navigation,
|
||||
updateLastLoad,
|
||||
pageSize,
|
||||
setPageSize,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const { t } = useTranslation('common')
|
||||
const { database, pushedTab } = useContext(AppContext)
|
||||
const { publicKey } = useContext(UserContext)
|
||||
const { lastEventId, relayPool, lastConfirmationtId } = useContext(RelayPoolContext)
|
||||
const initialPageSize = 10
|
||||
const [notes, setNotes] = useState<Note[]>()
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
const flashListRef = React.useRef<FlashList<Note>>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (pushedTab) {
|
||||
flashListRef.current?.scrollToIndex({ animated: true, index: 0 })
|
||||
}
|
||||
}, [pushedTab])
|
||||
|
||||
useEffect(() => {
|
||||
if (relayPool && publicKey) {
|
||||
loadNotes()
|
||||
}
|
||||
}, [lastEventId, lastConfirmationtId, relayPool, publicKey])
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
setRefreshing(true)
|
||||
updateLastLoad()
|
||||
}, [])
|
||||
|
||||
const onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void = (event) => {
|
||||
if (handleInfinityScroll(event)) {
|
||||
setPageSize(pageSize + initialPageSize)
|
||||
}
|
||||
}
|
||||
|
||||
const loadNotes: () => void = async () => {
|
||||
if (database && publicKey) {
|
||||
getMostZapedNotesContacts(database, getUnixTime(new Date()) - 86400).then((zaps) => {
|
||||
const zappedEventIds = zaps
|
||||
.map((zap) => zap.zapped_event_id)
|
||||
.filter((id) => id !== '')
|
||||
.slice(0, pageSize)
|
||||
if (zaps.length > 0) {
|
||||
relayPool?.subscribe('homepage-zapped-notes', [
|
||||
{
|
||||
kinds: [Kind.Text, Kind.RecommendRelay],
|
||||
ids: zappedEventIds,
|
||||
},
|
||||
])
|
||||
getNotes(database, { filters: { id: zappedEventIds } }).then((notes) => {
|
||||
setNotes(
|
||||
zappedEventIds
|
||||
.map((zappedEventId) => {
|
||||
return notes.find((note) => note && note.id === zappedEventId) as Note
|
||||
})
|
||||
.filter((note) => note !== undefined),
|
||||
)
|
||||
setRefreshing(false)
|
||||
if (notes.length > 0) {
|
||||
const noteIds = notes.map((note) => note.id ?? '')
|
||||
const authors = notes.map((note) => note.pubkey ?? '')
|
||||
const repostIds = notes
|
||||
.filter((note) => note.repost_id)
|
||||
.map((note) => note.repost_id ?? '')
|
||||
|
||||
const reactionFilters: RelayFilters[] = [
|
||||
{
|
||||
kinds: [Kind.Reaction, Kind.Text, 9735],
|
||||
'#e': noteIds,
|
||||
},
|
||||
]
|
||||
if (authors.length > 0) {
|
||||
reactionFilters.push({
|
||||
kinds: [Kind.Metadata],
|
||||
authors,
|
||||
})
|
||||
}
|
||||
relayPool?.subscribe('homepage-contacts-reactions', reactionFilters)
|
||||
if (repostIds.length > 0) {
|
||||
relayPool?.subscribe('homepage-contacts-reposts', [
|
||||
{
|
||||
kinds: [Kind.Text],
|
||||
ids: repostIds,
|
||||
},
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const renderItem: ListRenderItem<Note> = ({ item }) => {
|
||||
return (
|
||||
<View style={styles.noteCard} key={item.id}>
|
||||
<NoteCard note={item} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const ListEmptyComponent = React.useMemo(
|
||||
() => (
|
||||
<View style={styles.blank}>
|
||||
<MaterialCommunityIcons
|
||||
name='account-group-outline'
|
||||
size={64}
|
||||
style={styles.center}
|
||||
color={theme.colors.onPrimaryContainer}
|
||||
/>
|
||||
<Text variant='headlineSmall' style={styles.center}>
|
||||
{t('homeFeed.emptyTitle')}
|
||||
</Text>
|
||||
<Text variant='bodyMedium' style={styles.center}>
|
||||
{t('homeFeed.emptyDescription')}
|
||||
</Text>
|
||||
<Button mode='contained' compact onPress={() => navigation.jumpTo('contacts')}>
|
||||
{t('homeFeed.emptyButton')}
|
||||
</Button>
|
||||
</View>
|
||||
),
|
||||
[],
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={styles.list}>
|
||||
<FlashList
|
||||
showsVerticalScrollIndicator={false}
|
||||
data={notes}
|
||||
renderItem={renderItem}
|
||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
|
||||
onScroll={onScroll}
|
||||
refreshing={refreshing}
|
||||
ListEmptyComponent={notes ? ListEmptyComponent : <></>}
|
||||
horizontal={false}
|
||||
ListFooterComponent={
|
||||
notes && notes.length > 0 ? <ActivityIndicator style={styles.loading} animating={true} /> : <></>
|
||||
}
|
||||
ref={flashListRef}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
loading: {
|
||||
paddingTop: 16,
|
||||
},
|
||||
list: {
|
||||
height: '100%',
|
||||
},
|
||||
noteCard: {
|
||||
marginTop: 16,
|
||||
},
|
||||
center: {
|
||||
alignContent: 'center',
|
||||
textAlign: 'center',
|
||||
},
|
||||
blank: {
|
||||
justifyContent: 'space-between',
|
||||
height: 220,
|
||||
marginTop: 91,
|
||||
},
|
||||
activityIndicator: {
|
||||
padding: 16,
|
||||
},
|
||||
})
|
||||
|
||||
export default ZapsFeed
|
@ -1,137 +1,120 @@
|
||||
import React, { useContext } from 'react'
|
||||
import { Dimensions, ScrollView, StyleSheet, View } from 'react-native'
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import { Dimensions, StyleSheet, View } from 'react-native'
|
||||
import { UserContext } from '../../Contexts/UserContext'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { AnimatedFAB, Text, TouchableRipple } from 'react-native-paper'
|
||||
import { useFocusEffect, useTheme } from '@react-navigation/native'
|
||||
import { AnimatedFAB } from 'react-native-paper'
|
||||
import { useFocusEffect } from '@react-navigation/native'
|
||||
import { navigate } from '../../lib/Navigation'
|
||||
import { t } from 'i18next'
|
||||
import GlobalFeed from '../GlobalFeed'
|
||||
import MyFeed from '../MyFeed'
|
||||
import ReactionsFeed from '../ReactionsFeed'
|
||||
import RepostsFeed from '../RepostsFeed'
|
||||
import GlobalFeed from './GlobalFeed'
|
||||
import MyFeed from './MyFeed'
|
||||
import { getUnixTime } from 'date-fns'
|
||||
import { RelayFilters } from '../../lib/nostr/RelayPool/intex'
|
||||
import { Kind } from 'nostr-tools'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import { getUsers, User } from '../../Functions/DatabaseFunctions/Users'
|
||||
import Tabs from '../../Components/Tabs'
|
||||
import ZapsFeed from './ZapsFeed'
|
||||
|
||||
interface HomeFeedProps {
|
||||
navigation: any
|
||||
}
|
||||
|
||||
export const HomeFeed: React.FC<HomeFeedProps> = ({ navigation }) => {
|
||||
const theme = useTheme()
|
||||
const { privateKey } = useContext(UserContext)
|
||||
const initialPageSize = 10
|
||||
const { database } = useContext(AppContext)
|
||||
const { publicKey, privateKey } = useContext(UserContext)
|
||||
const { relayPool } = useContext(RelayPoolContext)
|
||||
const [tabKey, setTabKey] = React.useState('myFeed')
|
||||
const [activeTab, setActiveTab] = React.useState('myFeed')
|
||||
const [lastLoadAt, setLastLoadAt] = useState<number>(0)
|
||||
const [pageSize, setPageSize] = useState<number>(initialPageSize)
|
||||
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
subscribeNotes()
|
||||
subscribeGlobal()
|
||||
updateLastLoad()
|
||||
|
||||
return () =>
|
||||
relayPool?.unsubscribe([
|
||||
'homepage-global-main',
|
||||
'homepage-contacts-main',
|
||||
'homepage-reactions',
|
||||
'homepage-contacts-meta',
|
||||
'homepage-replies',
|
||||
'homepage-global-reposts',
|
||||
'homepage-myfeed-main',
|
||||
'homepage-myfeed-reactions',
|
||||
'homepage-myfeed-reposts',
|
||||
'homepage-zapped-notes',
|
||||
])
|
||||
}, []),
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (pageSize > initialPageSize) {
|
||||
subscribeGlobal(true)
|
||||
subscribeNotes(true)
|
||||
updateLastLoad()
|
||||
}
|
||||
}, [pageSize, lastLoadAt])
|
||||
|
||||
const updateLastLoad: () => void = () => {
|
||||
setLastLoadAt(getUnixTime(new Date()) - 5)
|
||||
}
|
||||
|
||||
const subscribeGlobal: (past?: boolean) => void = (past) => {
|
||||
const message: RelayFilters = {
|
||||
kinds: [Kind.Text, Kind.RecommendRelay],
|
||||
limit: pageSize,
|
||||
}
|
||||
|
||||
if (past) message.until = lastLoadAt
|
||||
|
||||
relayPool?.subscribe('homepage-global-main', [message])
|
||||
}
|
||||
|
||||
const subscribeNotes: (past?: boolean) => void = async (past) => {
|
||||
if (!database || !publicKey) return
|
||||
const users: User[] = await getUsers(database, { contacts: true, order: 'created_at DESC' })
|
||||
const authors: string[] = [...users.map((user) => user.id), publicKey]
|
||||
|
||||
const message: RelayFilters = {
|
||||
kinds: [Kind.Text, Kind.RecommendRelay],
|
||||
authors,
|
||||
limit: pageSize,
|
||||
}
|
||||
if (past) message.until = lastLoadAt
|
||||
relayPool?.subscribe('homepage-myfeed-main', [message])
|
||||
}
|
||||
|
||||
const renderScene: Record<string, JSX.Element> = {
|
||||
globalFeed: <GlobalFeed navigation={navigation} />,
|
||||
myFeed: <MyFeed navigation={navigation} />,
|
||||
reactions: <ReactionsFeed navigation={navigation} />,
|
||||
reposts: <RepostsFeed navigation={navigation} />,
|
||||
globalFeed: (
|
||||
<GlobalFeed
|
||||
navigation={navigation}
|
||||
updateLastLoad={updateLastLoad}
|
||||
lastLoadAt={lastLoadAt}
|
||||
pageSize={pageSize}
|
||||
setPageSize={setPageSize}
|
||||
/>
|
||||
),
|
||||
myFeed: (
|
||||
<MyFeed
|
||||
navigation={navigation}
|
||||
updateLastLoad={updateLastLoad}
|
||||
pageSize={pageSize}
|
||||
setPageSize={setPageSize}
|
||||
/>
|
||||
),
|
||||
zaps: (
|
||||
<ZapsFeed
|
||||
navigation={navigation}
|
||||
updateLastLoad={updateLastLoad}
|
||||
pageSize={pageSize}
|
||||
setPageSize={setPageSize}
|
||||
/>
|
||||
),
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.tabsNavigator}>
|
||||
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
||||
<View
|
||||
style={[
|
||||
styles.tab,
|
||||
{
|
||||
borderBottomColor:
|
||||
tabKey === 'globalFeed' ? theme.colors.primary : theme.colors.border,
|
||||
borderBottomWidth: tabKey === 'globalFeed' ? 3 : 1,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<TouchableRipple
|
||||
style={styles.textWrapper}
|
||||
onPress={() => {
|
||||
relayPool?.unsubscribe([
|
||||
'homepage-contacts-main',
|
||||
'homepage-reactions',
|
||||
'homepage-contacts-meta',
|
||||
'homepage-replies',
|
||||
])
|
||||
setTabKey('globalFeed')
|
||||
}}
|
||||
>
|
||||
<Text style={styles.tabText}>{t('homeFeed.globalFeed')}</Text>
|
||||
</TouchableRipple>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
styles.tab,
|
||||
{
|
||||
borderBottomColor: tabKey === 'myFeed' ? theme.colors.primary : theme.colors.border,
|
||||
borderBottomWidth: tabKey === 'myFeed' ? 3 : 1,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<TouchableRipple
|
||||
style={styles.textWrapper}
|
||||
onPress={() => {
|
||||
relayPool?.unsubscribe(['homepage-global-main'])
|
||||
setTabKey('myFeed')
|
||||
}}
|
||||
>
|
||||
<Text style={styles.tabText}>{t('homeFeed.myFeed')}</Text>
|
||||
</TouchableRipple>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
styles.tab,
|
||||
{
|
||||
borderBottomColor:
|
||||
tabKey === 'reactions' ? theme.colors.primary : theme.colors.border,
|
||||
borderBottomWidth: tabKey === 'reactions' ? 3 : 1,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<TouchableRipple
|
||||
style={styles.textWrapper}
|
||||
onPress={() => {
|
||||
relayPool?.unsubscribe(['homepage-global-main'])
|
||||
setTabKey('reactions')
|
||||
}}
|
||||
>
|
||||
<Text style={styles.tabText}>{t('homeFeed.reactions')}</Text>
|
||||
</TouchableRipple>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
styles.tab,
|
||||
{
|
||||
borderBottomColor:
|
||||
tabKey === 'reposts' ? theme.colors.primary : theme.colors.border,
|
||||
borderBottomWidth: tabKey === 'reposts' ? 3 : 1,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<TouchableRipple
|
||||
style={styles.textWrapper}
|
||||
onPress={() => {
|
||||
relayPool?.unsubscribe(['homepage-global-main'])
|
||||
setTabKey('reposts')
|
||||
}}
|
||||
>
|
||||
<Text style={styles.tabText}>{t('homeFeed.reposts')}</Text>
|
||||
</TouchableRipple>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
<View style={styles.feed}>{renderScene[tabKey]}</View>
|
||||
<Tabs tabs={['globalFeed', 'myFeed', 'zaps']} activeTab={activeTab} setActiveTab={setActiveTab} />
|
||||
<View style={styles.feed}>{renderScene[activeTab]}</View>
|
||||
{privateKey && (
|
||||
<AnimatedFAB
|
||||
style={[styles.fab, { top: Dimensions.get('window').height - 191 }]}
|
||||
|
@ -57,7 +57,7 @@ export const HomePage: React.FC = () => {
|
||||
relayPool?.subscribe('notification-icon', [
|
||||
{
|
||||
kinds: [Kind.ChannelMessage],
|
||||
'#e': [publicKey],
|
||||
'#p': [publicKey],
|
||||
limit: 30,
|
||||
},
|
||||
{
|
||||
@ -91,7 +91,7 @@ export const HomePage: React.FC = () => {
|
||||
const key = decode(clipboardNip21.replace('nostr:', ''))
|
||||
if (key?.data) {
|
||||
if (key.type === 'nevent') {
|
||||
navigate('Note', { noteId: key.data })
|
||||
navigate('Note', { noteId: key.data.id })
|
||||
} else if (key.type === 'npub') {
|
||||
navigate('Profile', { pubKey: key.data })
|
||||
} else if (key.type === 'nprofile' && key.data.pubkey) {
|
||||
|
@ -48,12 +48,12 @@ export const NotePage: React.FC<NotePageProps> = ({ route }) => {
|
||||
|
||||
const loadNote: () => void = async () => {
|
||||
if (database && publicKey) {
|
||||
const events = await getNotes(database, { filters: { id: route.params.noteId } })
|
||||
const events = await getNotes(database, { filters: { id: [route.params.noteId] } })
|
||||
if (events.length > 0) {
|
||||
const event = events[0]
|
||||
setNote(event)
|
||||
|
||||
const notes = await getNotes(database, { filters: { reply_event_id: route.params.noteId } })
|
||||
const notes = await getNotes(database, { filters: { reply_event_id: [route.params.noteId] } })
|
||||
const rootReplies = getDirectReplies(event, notes)
|
||||
const filters: RelayFilters[] = [
|
||||
{
|
||||
@ -117,7 +117,6 @@ export const NotePage: React.FC<NotePageProps> = ({ route }) => {
|
||||
<NoteCard note={note} />
|
||||
<View style={[styles.list, { borderColor: theme.colors.onSecondary }]}>
|
||||
<FlashList
|
||||
estimatedItemSize={200}
|
||||
showsVerticalScrollIndicator={false}
|
||||
data={replies}
|
||||
renderItem={renderItem}
|
||||
|
@ -167,7 +167,6 @@ export const NotificationsFeed: React.FC = () => {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<FlashList
|
||||
estimatedItemSize={200}
|
||||
showsVerticalScrollIndicator={false}
|
||||
data={notes}
|
||||
renderItem={renderItem}
|
||||
|
@ -29,6 +29,8 @@ export const ProfileLoadPage: React.FC = () => {
|
||||
setLastEventId(event.eventId),
|
||||
)
|
||||
setTimeout(() => loadMeta(), 1000)
|
||||
reloadUser()
|
||||
if (profileFound) loadPets()
|
||||
return () =>
|
||||
relayPool?.unsubscribe(['profile-load-meta', 'profile-load-notes', 'profile-load-others'])
|
||||
}, []),
|
||||
|
123
frontend/Pages/ProfilePage/NotesFeed/index.tsx
Normal file
123
frontend/Pages/ProfilePage/NotesFeed/index.tsx
Normal file
@ -0,0 +1,123 @@
|
||||
import React, { useContext, useState, useEffect } from 'react'
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import { AppContext } from '../../../Contexts/AppContext'
|
||||
import { getMainNotes, Note } from '../../../Functions/DatabaseFunctions/Notes'
|
||||
import { RelayPoolContext } from '../../../Contexts/RelayPoolContext'
|
||||
import { Kind } from 'nostr-tools'
|
||||
import { ActivityIndicator, Text, useTheme } from 'react-native-paper'
|
||||
import NoteCard from '../../../Components/NoteCard'
|
||||
import { FlashList, ListRenderItem } from '@shopify/flash-list'
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
interface NotesFeedProps {
|
||||
publicKey: string
|
||||
setRefreshing: (refreshing: boolean) => void
|
||||
pageSize: number
|
||||
refreshing: boolean
|
||||
}
|
||||
|
||||
export const NotesFeed: React.FC<NotesFeedProps> = ({ publicKey, pageSize, setRefreshing, refreshing }) => {
|
||||
const theme = useTheme()
|
||||
const { t } = useTranslation('common')
|
||||
const { database } = useContext(AppContext)
|
||||
const { lastEventId, relayPool } = useContext(RelayPoolContext)
|
||||
const [notes, setNotes] = useState<Note[]>([])
|
||||
const flashListRef = React.useRef<FlashList<Note>>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if(refreshing) loadNotes()
|
||||
}, [refreshing])
|
||||
|
||||
useEffect(() => {
|
||||
loadNotes()
|
||||
}, [pageSize, lastEventId])
|
||||
|
||||
const loadNotes: (main?: boolean) => void = () => {
|
||||
if (database) {
|
||||
getMainNotes(database, publicKey, pageSize).then((results) => {
|
||||
setNotes(results)
|
||||
setRefreshing(false)
|
||||
if (results.length > 0) {
|
||||
relayPool?.subscribe(`profile-notes-answers${publicKey.substring(0, 8)}`, [
|
||||
{
|
||||
kinds: [Kind.Reaction, Kind.Text, Kind.RecommendRelay, 9735],
|
||||
'#e': results.map((note) => note.id ?? ''),
|
||||
},
|
||||
])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const renderItem: ListRenderItem<Note> = ({ item }) => {
|
||||
return (
|
||||
<View style={styles.noteCard} key={item.id}>
|
||||
<NoteCard note={item} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const ListEmptyComponent = React.useMemo(
|
||||
() => (
|
||||
<View style={styles.blank}>
|
||||
<MaterialCommunityIcons
|
||||
name='lightning-bolt'
|
||||
size={64}
|
||||
style={styles.center}
|
||||
color={theme.colors.onPrimaryContainer}
|
||||
/>
|
||||
<Text variant='headlineSmall' style={styles.center}>
|
||||
{t('profilePage.repliesFeed.emptyTitle')}
|
||||
</Text>
|
||||
</View>
|
||||
),
|
||||
[],
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={styles.list}>
|
||||
<FlashList
|
||||
estimatedItemSize={210}
|
||||
showsVerticalScrollIndicator={false}
|
||||
data={notes}
|
||||
renderItem={renderItem}
|
||||
horizontal={false}
|
||||
ListFooterComponent={
|
||||
notes.length > 0 ? <ActivityIndicator style={styles.loading} animating={true} /> : <></>
|
||||
}
|
||||
ref={flashListRef}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
loading: {
|
||||
paddingTop: 16,
|
||||
},
|
||||
list: {
|
||||
height: '100%',
|
||||
},
|
||||
noteCard: {
|
||||
marginTop: 16,
|
||||
},
|
||||
center: {
|
||||
alignContent: 'center',
|
||||
textAlign: 'center',
|
||||
},
|
||||
blank: {
|
||||
justifyContent: 'space-between',
|
||||
height: 220,
|
||||
marginTop: 91,
|
||||
},
|
||||
activityIndicator: {
|
||||
padding: 16,
|
||||
},
|
||||
})
|
||||
|
||||
export default NotesFeed
|
127
frontend/Pages/ProfilePage/RepliesFeed/index.tsx
Normal file
127
frontend/Pages/ProfilePage/RepliesFeed/index.tsx
Normal file
@ -0,0 +1,127 @@
|
||||
import React, { useContext, useState, useEffect } from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { AppContext } from '../../../Contexts/AppContext'
|
||||
import { getReplyNotes, Note } from '../../../Functions/DatabaseFunctions/Notes'
|
||||
import { RelayPoolContext } from '../../../Contexts/RelayPoolContext'
|
||||
import { Kind } from 'nostr-tools'
|
||||
import { ActivityIndicator, Text, useTheme } from 'react-native-paper'
|
||||
import NoteCard from '../../../Components/NoteCard'
|
||||
import { FlashList, ListRenderItem } from '@shopify/flash-list'
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
interface RepliesFeedProps {
|
||||
publicKey: string
|
||||
setRefreshing: (refreshing: boolean) => void
|
||||
pageSize: number
|
||||
refreshing: boolean
|
||||
}
|
||||
|
||||
export const RepliesFeed: React.FC<RepliesFeedProps> = ({
|
||||
publicKey,
|
||||
pageSize,
|
||||
refreshing,
|
||||
setRefreshing,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const { t } = useTranslation('common')
|
||||
const { database } = useContext(AppContext)
|
||||
const { lastEventId, relayPool } = useContext(RelayPoolContext)
|
||||
const [notes, setNotes] = useState<Note[]>([])
|
||||
const flashListRef = React.useRef<FlashList<Note>>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (refreshing) loadNotes()
|
||||
}, [refreshing])
|
||||
|
||||
useEffect(() => {
|
||||
loadNotes()
|
||||
}, [pageSize, lastEventId])
|
||||
|
||||
const loadNotes: (main?: boolean) => void = () => {
|
||||
if (database) {
|
||||
getReplyNotes(database, publicKey, pageSize).then((results) => {
|
||||
setNotes(results)
|
||||
setRefreshing(false)
|
||||
if (results.length > 0) {
|
||||
relayPool?.subscribe(`profile-replies-answers${publicKey.substring(0, 8)}`, [
|
||||
{
|
||||
kinds: [Kind.Reaction, Kind.Text, Kind.RecommendRelay, 9735],
|
||||
'#e': results.map((note) => note.id ?? ''),
|
||||
},
|
||||
])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const renderItem: ListRenderItem<Note> = ({ item }) => {
|
||||
return (
|
||||
<View style={styles.noteCard} key={item.id}>
|
||||
<NoteCard note={item} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const ListEmptyComponent = React.useMemo(
|
||||
() => (
|
||||
<View style={styles.blank}>
|
||||
<MaterialCommunityIcons
|
||||
name='lightning-bolt'
|
||||
size={64}
|
||||
style={styles.center}
|
||||
color={theme.colors.onPrimaryContainer}
|
||||
/>
|
||||
<Text variant='headlineSmall' style={styles.center}>
|
||||
{t('profilePage.repliesFeed.emptyTitle')}
|
||||
</Text>
|
||||
</View>
|
||||
),
|
||||
[],
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={styles.list}>
|
||||
<FlashList
|
||||
estimatedItemSize={210}
|
||||
showsVerticalScrollIndicator={false}
|
||||
data={notes}
|
||||
renderItem={renderItem}
|
||||
horizontal={false}
|
||||
ListFooterComponent={
|
||||
notes.length > 0 ? <ActivityIndicator style={styles.loading} animating={true} /> : <></>
|
||||
}
|
||||
ref={flashListRef}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
loading: {
|
||||
paddingTop: 16,
|
||||
},
|
||||
list: {
|
||||
height: '100%',
|
||||
},
|
||||
noteCard: {
|
||||
marginTop: 16,
|
||||
},
|
||||
center: {
|
||||
alignContent: 'center',
|
||||
textAlign: 'center',
|
||||
},
|
||||
blank: {
|
||||
justifyContent: 'space-between',
|
||||
height: 220,
|
||||
marginTop: 91,
|
||||
},
|
||||
activityIndicator: {
|
||||
padding: 16,
|
||||
},
|
||||
})
|
||||
|
||||
export default RepliesFeed
|
150
frontend/Pages/ProfilePage/ZapsFeed/index.tsx
Normal file
150
frontend/Pages/ProfilePage/ZapsFeed/index.tsx
Normal file
@ -0,0 +1,150 @@
|
||||
import React, { useContext, useState, useEffect } from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { AppContext } from '../../../Contexts/AppContext'
|
||||
import { getNotes, Note } from '../../../Functions/DatabaseFunctions/Notes'
|
||||
import { RelayPoolContext } from '../../../Contexts/RelayPoolContext'
|
||||
import { Kind } from 'nostr-tools'
|
||||
import { ActivityIndicator, Text, useTheme } from 'react-native-paper'
|
||||
import NoteCard from '../../../Components/NoteCard'
|
||||
import { FlashList, ListRenderItem } from '@shopify/flash-list'
|
||||
import { getMostZapedNotes } from '../../../Functions/DatabaseFunctions/Zaps'
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getUnixTime } from 'date-fns'
|
||||
|
||||
interface ZapsFeedProps {
|
||||
publicKey: string
|
||||
setRefreshing: (refreshing: boolean) => void
|
||||
pageSize: number
|
||||
refreshing: boolean
|
||||
}
|
||||
|
||||
export const ZapsFeed: React.FC<ZapsFeedProps> = ({
|
||||
publicKey,
|
||||
pageSize,
|
||||
refreshing,
|
||||
setRefreshing,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const { t } = useTranslation('common')
|
||||
const { database } = useContext(AppContext)
|
||||
const { lastEventId, relayPool } = useContext(RelayPoolContext)
|
||||
const [notes, setNotes] = useState<Note[]>([])
|
||||
const flashListRef = React.useRef<FlashList<Note>>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (refreshing) loadNotes()
|
||||
}, [refreshing])
|
||||
|
||||
useEffect(() => {
|
||||
loadNotes()
|
||||
}, [pageSize, lastEventId])
|
||||
|
||||
const loadNotes: (main?: boolean) => void = () => {
|
||||
if (database) {
|
||||
getMostZapedNotes(database, publicKey, pageSize, getUnixTime(new Date()) - 604800).then((zaps) => {
|
||||
const zappedEventIds = zaps.map((zap) => zap.zapped_event_id)
|
||||
if (zaps.length > 0) {
|
||||
relayPool?.subscribe(`profile-zap-notes${publicKey.substring(0, 8)}`, [
|
||||
{
|
||||
kinds: [Kind.Text, Kind.RecommendRelay],
|
||||
ids: zappedEventIds,
|
||||
},
|
||||
])
|
||||
getNotes(database, { filters: { id: zappedEventIds } }).then((results) => {
|
||||
if (results.length > 0) {
|
||||
setNotes(
|
||||
zappedEventIds
|
||||
.map((zappedEventId) => {
|
||||
return results.find((note) => note && note.id === zappedEventId) as Note
|
||||
})
|
||||
.filter((note) => note !== undefined),
|
||||
)
|
||||
setRefreshing(false)
|
||||
if (results.length > 0) {
|
||||
relayPool?.subscribe(`profile-zaps-answers${publicKey.substring(0, 8)}`, [
|
||||
{
|
||||
kinds: [Kind.Reaction, Kind.Text, Kind.RecommendRelay, 9735],
|
||||
'#e': results.map((note) => note.id ?? ''),
|
||||
},
|
||||
{
|
||||
kinds: [Kind.Metadata],
|
||||
ids: zappedEventIds,
|
||||
},
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const renderItem: ListRenderItem<Note> = ({ item }) => {
|
||||
return (
|
||||
<View style={styles.noteCard} key={item.id}>
|
||||
<NoteCard note={item} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const ListEmptyComponent = React.useMemo(
|
||||
() => (
|
||||
<View style={styles.blank}>
|
||||
<MaterialCommunityIcons
|
||||
name='lightning-bolt'
|
||||
size={64}
|
||||
style={styles.center}
|
||||
color={theme.colors.onPrimaryContainer}
|
||||
/>
|
||||
<Text variant='headlineSmall' style={styles.center}>
|
||||
{t('profilePage.zapsFeed.emptyTitle')}
|
||||
</Text>
|
||||
</View>
|
||||
),
|
||||
[],
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={styles.list}>
|
||||
<FlashList
|
||||
estimatedItemSize={210}
|
||||
showsVerticalScrollIndicator={false}
|
||||
data={notes}
|
||||
renderItem={renderItem}
|
||||
horizontal={false}
|
||||
ListFooterComponent={
|
||||
notes.length > 0 ? <ActivityIndicator style={styles.loading} animating={true} /> : <></>
|
||||
}
|
||||
ref={flashListRef}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
loading: {
|
||||
paddingTop: 16,
|
||||
},
|
||||
list: {
|
||||
height: '100%',
|
||||
},
|
||||
noteCard: {
|
||||
marginTop: 16,
|
||||
},
|
||||
center: {
|
||||
alignContent: 'center',
|
||||
textAlign: 'center',
|
||||
},
|
||||
blank: {
|
||||
justifyContent: 'space-between',
|
||||
height: 120,
|
||||
marginTop: 91,
|
||||
},
|
||||
activityIndicator: {
|
||||
padding: 16,
|
||||
},
|
||||
})
|
||||
|
||||
export default ZapsFeed
|
@ -7,22 +7,22 @@ import {
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import { Surface, Text, ActivityIndicator, Snackbar, Divider } from 'react-native-paper'
|
||||
import { FlashList, ListRenderItem } from '@shopify/flash-list'
|
||||
import { Surface, Text, Snackbar } from 'react-native-paper'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import { UserContext } from '../../Contexts/UserContext'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { getNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
|
||||
import { getUser, User } from '../../Functions/DatabaseFunctions/Users'
|
||||
import { Kind } from 'nostr-tools'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RelayFilters } from '../../lib/nostr/RelayPool/intex'
|
||||
import NoteCard from '../../Components/NoteCard'
|
||||
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
|
||||
import { useFocusEffect } from '@react-navigation/native'
|
||||
import ProfileData from '../../Components/ProfileData'
|
||||
import ProfileActions from '../../Components/ProfileActions'
|
||||
import TextContent from '../../Components/TextContent'
|
||||
import Tabs from '../../Components/Tabs'
|
||||
import NotesFeed from './NotesFeed'
|
||||
import RepliesFeed from './RepliesFeed'
|
||||
import ZapsFeed from './ZapsFeed'
|
||||
import { getUnixTime } from 'date-fns'
|
||||
|
||||
interface ProfilePageProps {
|
||||
route: { params: { pubKey: string } }
|
||||
@ -33,47 +33,47 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
|
||||
const { publicKey } = useContext(UserContext)
|
||||
const { lastEventId, relayPool } = useContext(RelayPoolContext)
|
||||
const { t } = useTranslation('common')
|
||||
const initialPageSize = 10
|
||||
const initialPageSize = 20
|
||||
const [showNotification, setShowNotification] = useState<undefined | string>()
|
||||
const [notes, setNotes] = useState<Note[]>()
|
||||
const [user, setUser] = useState<User>()
|
||||
const [pageSize, setPageSize] = useState<number>(initialPageSize)
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
const [firstLoad, setFirstLoad] = useState(true)
|
||||
const [refreshing, setRefreshing] = useState<boolean>(false)
|
||||
const [firstLoad, setFirstLoad] = useState<boolean>(true)
|
||||
const [activeTab, setActiveTab] = React.useState('notes')
|
||||
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
subscribeProfile()
|
||||
subscribeNotes()
|
||||
loadUser()
|
||||
loadNotes()
|
||||
setFirstLoad(false)
|
||||
|
||||
return () =>
|
||||
relayPool?.unsubscribe([
|
||||
`profile${route.params.pubKey}`,
|
||||
`profile-user${route.params.pubKey}`,
|
||||
`profile-answers${route.params.pubKey}`,
|
||||
`profile-user${route.params.pubKey.substring(0, 8)}`,
|
||||
`profile-zaps${route.params.pubKey.substring(0, 8)}`,
|
||||
`profile-zap-notes${route.params.pubKey.substring(0, 8)}`,
|
||||
`profile-replies-answers${route.params.pubKey.substring(0, 8)}`,
|
||||
`profile-notes-answers${route.params.pubKey.substring(0, 8)}`,
|
||||
])
|
||||
}, []),
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!firstLoad) {
|
||||
if (pageSize > initialPageSize) {
|
||||
subscribeNotes(true)
|
||||
}
|
||||
loadUser()
|
||||
loadNotes()
|
||||
if (!firstLoad && pageSize > initialPageSize) {
|
||||
subscribeProfile()
|
||||
}
|
||||
}, [pageSize, lastEventId])
|
||||
}, [pageSize])
|
||||
|
||||
useEffect(() => {
|
||||
if (!firstLoad) {
|
||||
loadUser()
|
||||
}
|
||||
}, [pageSize, lastEventId, activeTab])
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
setRefreshing(true)
|
||||
loadUser()
|
||||
loadNotes()
|
||||
subscribeProfile()
|
||||
subscribeNotes()
|
||||
}, [])
|
||||
|
||||
const loadUser: () => void = () => {
|
||||
@ -90,25 +90,6 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const loadNotes: (past?: boolean) => void = () => {
|
||||
if (database) {
|
||||
getNotes(database, { filters: { pubkey: route.params.pubKey }, limit: pageSize }).then(
|
||||
(results) => {
|
||||
setNotes(results)
|
||||
setRefreshing(false)
|
||||
if (results.length > 0) {
|
||||
relayPool?.subscribe(`profile-answers${route.params.pubKey.substring(0, 8)}`, [
|
||||
{
|
||||
kinds: [Kind.Reaction, Kind.Text, Kind.RecommendRelay, 9735],
|
||||
'#e': results.map((note) => note.id ?? ''),
|
||||
},
|
||||
])
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const subscribeProfile: () => Promise<void> = async () => {
|
||||
relayPool?.subscribe(`profile-user${route.params.pubKey.substring(0, 8)}`, [
|
||||
{
|
||||
@ -119,31 +100,51 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
|
||||
kinds: [Kind.Contacts],
|
||||
authors: [route.params.pubKey],
|
||||
},
|
||||
{
|
||||
kinds: [Kind.Text, Kind.RecommendRelay],
|
||||
authors: [route.params.pubKey],
|
||||
limit: pageSize,
|
||||
},
|
||||
{
|
||||
kinds: [9735],
|
||||
"#p": [route.params.pubKey],
|
||||
since: getUnixTime(new Date()) - 604800
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
const subscribeNotes: (past?: boolean) => void = (past) => {
|
||||
if (!database) return
|
||||
|
||||
const message: RelayFilters = {
|
||||
kinds: [Kind.Text, Kind.RecommendRelay],
|
||||
authors: [route.params.pubKey],
|
||||
limit: pageSize,
|
||||
}
|
||||
relayPool?.subscribe(`profile${route.params.pubKey.substring(0, 8)}`, [message])
|
||||
}
|
||||
|
||||
const onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void = (event) => {
|
||||
if (handleInfinityScroll(event)) {
|
||||
setPageSize(pageSize + initialPageSize)
|
||||
}
|
||||
}
|
||||
|
||||
const renderItem: ListRenderItem<Note> = ({ item }) => (
|
||||
<View style={styles.noteCard} key={item.id}>
|
||||
<NoteCard note={item} />
|
||||
</View>
|
||||
)
|
||||
const renderScene: Record<string, JSX.Element> = {
|
||||
notes: (
|
||||
<NotesFeed
|
||||
publicKey={route.params.pubKey}
|
||||
pageSize={pageSize}
|
||||
setRefreshing={setRefreshing}
|
||||
refreshing={refreshing}
|
||||
/>
|
||||
),
|
||||
replies: (
|
||||
<RepliesFeed
|
||||
publicKey={route.params.pubKey}
|
||||
pageSize={pageSize}
|
||||
setRefreshing={setRefreshing}
|
||||
refreshing={refreshing}
|
||||
/>
|
||||
),
|
||||
zaps: (
|
||||
<ZapsFeed
|
||||
publicKey={route.params.pubKey}
|
||||
pageSize={pageSize}
|
||||
setRefreshing={setRefreshing}
|
||||
refreshing={refreshing}
|
||||
/>
|
||||
),
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
@ -173,30 +174,17 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
|
||||
<View>
|
||||
<TextContent content={user?.about} showPreview={false} numberOfLines={10} />
|
||||
</View>
|
||||
<Divider style={styles.divider} />
|
||||
{/* <Divider style={styles.divider} />
|
||||
<View style={styles.profileActions}>
|
||||
{user && <ProfileActions user={user} setUser={setUser} />}
|
||||
</View>
|
||||
</View> */}
|
||||
</Surface>
|
||||
<View style={styles.list}>
|
||||
<FlashList
|
||||
estimatedItemSize={200}
|
||||
showsVerticalScrollIndicator={false}
|
||||
data={notes}
|
||||
renderItem={renderItem}
|
||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
|
||||
onScroll={onScroll}
|
||||
refreshing={refreshing}
|
||||
horizontal={false}
|
||||
ListFooterComponent={
|
||||
notes && notes.length > 0 ? (
|
||||
<ActivityIndicator style={styles.loading} animating={true} />
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<Tabs
|
||||
tabs={['notes', 'replies', 'zaps']}
|
||||
activeTab={activeTab}
|
||||
setActiveTab={setActiveTab}
|
||||
/>
|
||||
<View style={styles.list}>{renderScene[activeTab]}</View>
|
||||
</ScrollView>
|
||||
{showNotification && (
|
||||
<Snackbar
|
||||
|
@ -156,7 +156,6 @@ export const ReactionsFeed: React.FC<ReactionsFeedProps> = ({ navigation }) => {
|
||||
return (
|
||||
<View style={styles.list}>
|
||||
<FlashList
|
||||
estimatedItemSize={200}
|
||||
showsVerticalScrollIndicator={false}
|
||||
data={notes}
|
||||
renderItem={renderItem}
|
||||
|
@ -155,7 +155,6 @@ export const RepostsFeed: React.FC<RepostsFeedProps> = ({ navigation }) => {
|
||||
return (
|
||||
<View style={styles.list}>
|
||||
<FlashList
|
||||
estimatedItemSize={200}
|
||||
showsVerticalScrollIndicator={false}
|
||||
data={notes}
|
||||
renderItem={renderItem}
|
||||
|
Loading…
Reference in New Issue
Block a user