Global feed relay filter (#238)

This commit is contained in:
KoalaSat 2023-02-03 13:49:41 +00:00 committed by GitHub
commit 951fda5737
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 262 additions and 208 deletions

View File

@ -298,6 +298,7 @@ public class Event {
values.put("name", name);
values.put("contact", true);
values.put("blocked", 0);
values.put("pet_at", created_at);
database.insert("nostros_users", null, values);
}
}
@ -315,6 +316,7 @@ public class Event {
if (cursor.getCount() == 0) {
values.put("id", pubkey);
values.put("follower", true);
values.put("follower_at", created_at);
database.insert("nostros_users", null, values);
} else {
String whereClause = "id = ?";
@ -322,6 +324,7 @@ public class Event {
this.pubkey
};
values.put("follower", true);
values.put("follower_at", created_at);
database.update("nostros_users", values, whereClause, whereArgs);
}
}

View File

@ -13,22 +13,28 @@ import java.io.IOException;
public class Relay {
private Websocket webSocket;
public String url;
public boolean active;
public int active;
public int globalFeed;
public Relay(String serverUrl, boolean isActive,DatabaseModule database, ReactApplicationContext reactContext) throws IOException {
public Relay(String serverUrl, int isActive, int showGlobalFeed, DatabaseModule database, ReactApplicationContext reactContext) throws IOException {
webSocket = new Websocket(serverUrl, database, reactContext);
url = serverUrl;
active = isActive;
globalFeed = showGlobalFeed;
}
public boolean isActive() {
public int active() {
return active;
}
public void setActive(boolean active) {
public void setActive(int active) {
this.active = active;
}
public void setGlobalFeed(int globalFeed) {
this.globalFeed = globalFeed;
}
public void send(String message) {
webSocket.send(message);
}
@ -44,7 +50,8 @@ public class Relay {
public void save(SQLiteDatabase database) {
ContentValues values = new ContentValues();
values.put("url", url);
values.put("active", active ? 1 : 0);
values.put("active", active);
values.put("global_feed", globalFeed);
database.replace("nostros_relays", null, values);
}

View File

@ -40,8 +40,8 @@ public class DatabaseModule {
" picture TEXT,\n" +
" about TEXT,\n" +
" main_relay TEXT,\n" +
" contact BOOLEAN DEFAULT FALSE,\n" +
" follower BOOLEAN DEFAULT FALSE\n" +
" contact BOOLEAN DEFAULT 0,\n" +
" follower BOOLEAN DEFAULT 0\n" +
" );");
database.execSQL("CREATE TABLE IF NOT EXISTS nostros_relays(\n" +
" url TEXT PRIMARY KEY NOT NULL,\n" +
@ -56,7 +56,7 @@ public class DatabaseModule {
" sig TEXT NOT NULL,\n" +
" tags TEXT NOT NULL,\n" +
" conversation_id TEXT NOT NULL,\n" +
" read BOOLEAN DEFAULT FALSE\n" +
" read BOOLEAN DEFAULT 0\n" +
" );");
try {
database.execSQL("ALTER TABLE nostros_notes ADD COLUMN user_mentioned BOOLEAN DEFAULT 0;");
@ -139,6 +139,11 @@ public class DatabaseModule {
database.execSQL("CREATE INDEX nostros_notes_relays_note_id_index ON nostros_notes_relays(note_id);");
database.execSQL("CREATE INDEX nostros_notes_relays_pubkey_index ON nostros_notes_relays(pubkey);");
} catch (SQLException e) { }
try {
database.execSQL("ALTER TABLE nostros_relays ADD COLUMN global_feed BOOLEAN DEFAULT 1;");
database.execSQL("ALTER TABLE nostros_users ADD COLUMN pet_at INT;");
database.execSQL("ALTER TABLE nostros_users ADD COLUMN follower_at INT;");
} catch (SQLException e) { }
}
public void saveEvent(JSONObject data, String userPubKey, String relayUrl) throws JSONException {
@ -156,7 +161,7 @@ public class DatabaseModule {
public List<Relay> getRelays(ReactApplicationContext reactContext) {
List<Relay> relayList = new ArrayList<>();
String query = "SELECT url, active FROM nostros_relays;";
String query = "SELECT url, active, global_feed FROM nostros_relays;";
@SuppressLint("Recycle") Cursor cursor = database.rawQuery(query, new String[] {});
if (cursor.getCount() > 0) {
cursor.moveToFirst();
@ -164,7 +169,8 @@ public class DatabaseModule {
try {
String relayUrl = cursor.getString(0);
int active = cursor.getInt(1);
Relay relay = new Relay(relayUrl, active > 0,this, reactContext);
int globalFeed = cursor.getInt(2);
Relay relay = new Relay(relayUrl, active, globalFeed,this, reactContext);
relayList.add(relay);
} catch (IOException e) {
Log.d("WebSocket", e.toString());

View File

@ -38,7 +38,7 @@ public class RelayPoolModule extends ReactContextBaseJavaModule {
@ReactMethod
public void add(String url) {
try {
Relay relay = new Relay(url, true, database, context);
Relay relay = new Relay(url, 1, 1, database, context);
relay.connect(userPubKey);
relays.add(relay);
database.saveRelay(relay);
@ -63,15 +63,16 @@ public class RelayPoolModule extends ReactContextBaseJavaModule {
}
@ReactMethod
public void active(String url, Callback callback) throws IOException {
public void update(String url, int active, int globalFeed, Callback callback) throws IOException {
ListIterator<Relay> iterator = relays.listIterator();
boolean relayExists = false;
while(iterator.hasNext()){
Relay relay = iterator.next();
if(url.equals(relay.url) && !relay.isActive()){
if(url.equals(relay.url)){
int index = relays.indexOf(relay);
relay.connect(userPubKey);
relay.setActive(true);
relay.setActive(active);
relay.setGlobalFeed(globalFeed);
relay.save(database.database);
this.relays.set(index, relay);
relayExists = true;
@ -85,30 +86,13 @@ public class RelayPoolModule extends ReactContextBaseJavaModule {
callback.invoke();
}
@ReactMethod
public void desactive(String url, Callback callback) {
ListIterator<Relay> iterator = relays.listIterator();
while(iterator.hasNext()){
Relay relay = iterator.next();
if(url.equals(relay.url) && relay.isActive()){
int index = relays.indexOf(relay);
relay.disconnect();
relay.setActive(false);
relay.save(database.database);
this.relays.set(index, relay);
}
}
callback.invoke();
}
@ReactMethod
public void connect(String pubKey, Callback callback) {
userPubKey = pubKey;
relays = database.getRelays(context);
for (Relay relay : relays) {
try {
if (relay.isActive()) {
if (relay.active() > 0) {
relay.connect(pubKey);
}
} catch (IOException e) {
@ -119,9 +103,9 @@ public class RelayPoolModule extends ReactContextBaseJavaModule {
}
@ReactMethod
public void send(String message) {
public void send(String message, boolean globalFeed) {
for (Relay relay : relays) {
if (relay.isActive()) {
if (relay.active() > 0 && (!globalFeed || relay.globalFeed > 0)) {
relay.send(message);
}
}

View File

@ -256,7 +256,7 @@ export const NoteCard: React.FC<NoteCardProps> = ({
}
const getNoteContent: () => JSX.Element | undefined = () => {
if (note?.blocked !== undefined && note.blocked) {
if (note?.blocked !== undefined && note.blocked > 0) {
return blockedContent()
} else if (note?.kind === Kind.Text) {
return textNote()
@ -289,7 +289,7 @@ export const NoteCard: React.FC<NoteCardProps> = ({
)}
</Card.Content>
{getNoteContent()}
{showAction && !note?.blocked && (
{showAction && !note?.blocked > 0 && (
<Card.Content style={[styles.bottomActions, { borderColor: theme.colors.onSecondary }]}>
<Button
icon={() => (

View File

@ -39,7 +39,7 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({
const { publicKey } = React.useContext(UserContext)
const { relayPool } = React.useContext(RelayPoolContext)
const [user, setUser] = React.useState<User>()
const [blocked, setBlocked] = React.useState<boolean>()
const [blocked, setBlocked] = React.useState<number>()
const [openLn, setOpenLn] = React.useState<boolean>(false)
const [isContact, setIsContact] = React.useState<boolean>()
const [showNotification, setShowNotification] = React.useState<undefined | string>()
@ -54,7 +54,7 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({
const onChangeBlockUser: () => void = () => {
if (database && blocked !== undefined) {
updateUserBlock(userPubKey, database, !blocked).then(() => {
setBlocked(!blocked)
setBlocked(blocked === 0 ? 1 : 0)
loadUser()
})
}
@ -85,11 +85,11 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({
getUser(userPubKey, database).then((result) => {
if (result) {
setUser(result)
setBlocked(result.blocked !== undefined && result.blocked)
setBlocked(result.blocked)
setIsContact(result?.contact)
} else {
setUser({ id: userPubKey })
setBlocked(false)
setBlocked(0)
}
})
}
@ -199,11 +199,11 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({
)}
<View style={styles.actionButton}>
<IconButton
icon={blocked ? 'account-cancel' : 'account-cancel-outline'}
icon={blocked && blocked > 0 ? 'account-cancel' : 'account-cancel-outline'}
size={28}
onPress={onChangeBlockUser}
/>
<Text>{t(blocked ? 'profileCard.unblock' : 'profileCard.block')}</Text>
<Text>{t(blocked && blocked > 0 ? 'profileCard.unblock' : 'profileCard.block')}</Text>
</View>
</View>
{showNotification && (

View File

@ -15,8 +15,7 @@ export interface RelayPoolContextProps {
relays: Relay[]
addRelayItem: (relay: Relay) => Promise<void>
removeRelayItem: (relay: Relay) => Promise<void>
activeRelayItem: (relay: Relay) => Promise<void>
desactiveRelayItem: (relay: Relay) => Promise<void>
updateRelayItem: (relay: Relay) => Promise<void>
}
export interface WebsocketEvent {
@ -33,8 +32,7 @@ export const initialRelayPoolContext: RelayPoolContextProps = {
setRelayPool: () => {},
addRelayItem: async () => await new Promise(() => {}),
removeRelayItem: async () => await new Promise(() => {}),
activeRelayItem: async () => await new Promise(() => {}),
desactiveRelayItem: async () => await new Promise(() => {}),
updateRelayItem: async () => await new Promise(() => {}),
relays: [],
}
@ -91,32 +89,19 @@ export const RelayPoolContextProvider = ({
})
}
const activeRelayItem: (relay: Relay) => Promise<void> = async (relay) => {
const updateRelayItem: (relay: Relay) => Promise<void> = async (relay) => {
setRelays((prev) => {
return prev.map((item) => {
if (item.url === relay.url) item.active = true
return item
if (item.url === relay.url) {
return relay
} else {
return item
}
})
})
return await new Promise((resolve, _reject) => {
if (relayPool && database && publicKey) {
relayPool.active(relay.url, () => {
loadRelays().then(resolve)
})
}
})
}
const desactiveRelayItem: (relay: Relay) => Promise<void> = async (relay) => {
setRelays((prev) => {
return prev.map((item) => {
if (item.url === relay.url) item.active = false
return item
})
})
return await new Promise((resolve, _reject) => {
if (relayPool && database && publicKey) {
relayPool.desactive(relay.url, () => {
relayPool.update(relay.url, relay.active ?? 1, relay.global_feed ?? 1, () => {
loadRelays().then(resolve)
})
}
@ -174,8 +159,7 @@ export const RelayPoolContextProvider = ({
relays,
addRelayItem,
removeRelayItem,
activeRelayItem,
desactiveRelayItem,
updateRelayItem,
}}
>
{children}

View File

@ -11,7 +11,7 @@ export interface Note extends Event {
nip05: string
valid_nip05: boolean
repost_id: string
blocked: boolean
blocked: number
}
export interface NoteRelay {
@ -73,8 +73,13 @@ export const getMainNotesCount: (
const repliesQuery = `
SELECT
COUNT(*)
FROM nostros_notes
WHERE created_at > "${from}"
FROM nostros_notes
LEFT JOIN
nostros_users ON nostros_users.id = nostros_notes.pubkey
WHERE
nostros_users.blocked != 1 AND
nostros_notes.main_event_id IS NULL AND
nostros_notes.created_at > "${from}"
`
const resultSet = db.execute(repliesQuery)
const item: { 'COUNT(*)': number } = resultSet?.rows?.item(0)

View File

@ -5,6 +5,7 @@ export interface Relay {
url: string
name?: string
active?: number
global_feed?: number
}
const databaseToEntity: (object: any) => Relay = (object) => {

View File

@ -13,7 +13,7 @@ export interface User {
nip05?: string
created_at?: number
valid_nip05?: boolean
blocked?: boolean
blocked?: number
}
const databaseToEntity: (object: object) => User = (object) => {

View File

@ -153,6 +153,9 @@
"myFeed": "Mein Feed"
},
"relaysPage": {
"relayName": "Address",
"globalFeed": "Global feed",
"active": "Active",
"labelAdd": "Beschreibung des Relay",
"cancel": "Abbrechen",
"add": "Hinzufügen",
@ -163,7 +166,8 @@
"remove": "Relay entfernen",
"active": "Relay aktivieren.",
"desactive": "Relay deaktivieren.",
"badFormat": "Die URL des Relays hat kein gültiges Format."
"badFormat": "Die URL des Relays hat kein gültiges Format.",
"alreadyExists": "Relay already exists."
}
},
"profileConfigPage": {

View File

@ -163,6 +163,9 @@
"newMessages": "{{newNotesCount}} new notes. Pull to refresh."
},
"relaysPage": {
"relayName": "Address",
"globalFeed": "Global feed",
"active": "Active",
"labelAdd": "Relay address",
"cancel": "Cancel",
"add": "Add",
@ -171,11 +174,14 @@
"recommended": "Recommended",
"myList": "My relays",
"notifications": {
"globalFeedActive": "Global feed enabled.",
"globalFeedActiveUnactive": "Global feed disabled.",
"add": "Relay added.",
"remove": "Relay removed.",
"active": "Relay activated.",
"desactive": "Relay desactivated.",
"badFormat": "Relay URL has a bad format."
"badFormat": "Relay URL has a bad format.",
"alreadyExists": "Relay already exists."
}
},
"profileConfigPage": {

View File

@ -2,7 +2,7 @@
"common": {
"drawers": {
"relaysTitle": "Relés",
"relaysDescription": "Los relés son nodos en la red que actúan como intermediarios para la transmisión demensajes entre aplicaciones.\n\n\nLos relés pueden ser utilizados para mejorar la resiliencia yla disponibilidad de la red, ya que permiten que los mensajes sean entregados aun cuandohay fallos o interrupciones en la conectividad.\n\n\nLos relés también pueden ser utilizadospara mejorar la privacidad y la seguridad de la red, ya que pueden ocultar la ubicación yel identidad de las aplicaciones que se comunican entre sí a través de ellos. Esto puedeser útil en entornos donde la censura o la vigilancia son un problema.\n\n\nEs importante teneren cuenta que los relés también pueden ser utilizados para propósitos malintencionados,como para rastrear o censurar el tráfico de la red.\n\n\nPor lo tanto, es importante evaluarcuidadosamente el uso de relés y considerar medidas de seguridad adecuadas para protegerla privacidad y la seguridad de la red.",
"relaysDescription": "Los relays son nodos en la red que actúan como intermediarios para la transmisión demensajes entre aplicaciones.\n\n\nLos relays pueden ser utilizados para mejorar la resiliencia yla disponibilidad de la red, ya que permiten que los mensajes sean entregados aun cuandohay fallos o interrupciones en la conectividad.\n\n\nLos relays también pueden ser utilizadospara mejorar la privacidad y la seguridad de la red, ya que pueden ocultar la ubicación yel identidad de las aplicaciones que se comunican entre sí a través de ellos. Esto puedeser útil en entornos donde la censura o la vigilancia son un problema.\n\n\nEs importante teneren cuenta que los relays también pueden ser utilizados para propósitos malintencionados,como para rastrear o censurar el tráfico de la red.\n\n\nPor lo tanto, es importante evaluarcuidadosamente el uso de relays y considerar medidas de seguridad adecuadas para protegerla privacidad y la seguridad de la red.",
"keysTitle": "¿Qué son las claves?",
"keysDescription": "En nostr tienes dos claves: tu clave pública y tu clave privada.",
"publicKeys": "Clave pública",
@ -153,6 +153,9 @@
"myFeed": "Mi feed"
},
"relaysPage": {
"relayName": "Dirección",
"globalFeed": "Feed global",
"active": "Activo",
"labelAdd": "Dirección de relay",
"cancel": "Cancelar",
"add": "Añadir",
@ -163,7 +166,8 @@
"remove": "Relay borrado",
"active": "Relay activado.",
"desactive": "Relay desactivado.",
"badFormat": "La URL del relay no tiene el formato adecuado."
"badFormat": "La URL del relay no tiene el formato adecuado.",
"alreadyExists": "El relay ya existe."
}
},
"profileConfigPage": {

View File

@ -152,6 +152,9 @@
"myFeed": "Mon flux"
},
"relaysPage": {
"relayName": "Address",
"globalFeed": "Global feed",
"active": "Active",
"labelAdd": "Adresse du relai",
"cancel": "Annuler",
"add": "Ajouter",
@ -162,7 +165,8 @@
"remove": "Relai supprimé",
"active": "Relais activé.",
"desactive": "Relais desactivé.",
"badFormat": "L'URL du relai n'est pas correctement formatée."
"badFormat": "L'URL du relai n'est pas correctement formatée.",
"alreadyExists": "Relay already exists."
}
},
"profileConfigPage": {

View File

@ -152,6 +152,9 @@
"myFeed": "My feed"
},
"relaysPage": {
"relayName": "Address",
"globalFeed": "Global feed",
"active": "Active",
"labelAdd": "Relay address",
"cancel": "Отменить",
"add": "Добавить",
@ -162,7 +165,8 @@
"remove": "Relay removed.",
"active": "Relay acivado.",
"desactive": "Relay desactivado.",
"badFormat": "Relay URL has a bad format."
"badFormat": "Relay URL has a bad format.",
"alreadyExists": "Relay already exists."
}
},
"profileConfigPage": {

View File

@ -235,6 +235,7 @@ export const ContactsFeed: React.FC = () => {
const Following: JSX.Element = (
<View style={styles.container}>
<FlashList
estimatedItemSize={71}
showsVerticalScrollIndicator={false}
data={following.slice(0, pageSize)}
renderItem={renderContactItem}
@ -276,6 +277,7 @@ export const ContactsFeed: React.FC = () => {
const Followers: JSX.Element = (
<View style={styles.container}>
<FlashList
estimatedItemSize={71}
style={styles.list}
data={followers.slice(0, pageSize)}
renderItem={renderContactItem}

View File

@ -97,7 +97,7 @@ export const GlobalFeed: React.FC<GlobalFeedProps> = ({ navigation, setProfileCa
setRefreshing(false)
if (results.length > 0) {
setNotes(results)
relayPool?.subscribe('homepage-contacts-meta', [
relayPool?.subscribe('homepage-global-meta', [
{
kinds: [Kind.Metadata],
authors: results.map((note) => note.pubkey ?? ''),
@ -169,6 +169,7 @@ export const GlobalFeed: React.FC<GlobalFeedProps> = ({ navigation, setProfileCa
)}
<View style={styles.list}>
<FlashList
estimatedItemSize={200}
showsVerticalScrollIndicator={false}
data={notes}
renderItem={renderItem}

View File

@ -164,6 +164,7 @@ export const MyFeed: React.FC<MyFeedProps> = ({ navigation, setProfileCardPubKey
return (
<View style={styles.list}>
<FlashList
estimatedItemSize={200}
showsVerticalScrollIndicator={false}
data={notes}
renderItem={renderItem}

View File

@ -136,6 +136,7 @@ export const NotePage: React.FC<NotePageProps> = ({ route }) => {
<NoteCard note={note} onPressUser={openProfileDrawer} />
<View style={[styles.list, { borderColor: theme.colors.onSecondary }]}>
<FlashList
estimatedItemSize={200}
showsVerticalScrollIndicator={false}
data={replies}
renderItem={renderItem}

View File

@ -3,7 +3,6 @@ import {
NativeScrollEvent,
NativeSyntheticEvent,
RefreshControl,
ScrollView,
StyleSheet,
View,
} from 'react-native'
@ -25,6 +24,7 @@ import { useFocusEffect } from '@react-navigation/native'
import { getLastReaction } from '../../Functions/DatabaseFunctions/Reactions'
import { getUnixTime } from 'date-fns'
import { Config } from '../../Functions/DatabaseFunctions/Config'
import { FlashList, ListRenderItem } from '@shopify/flash-list'
export const NotificationsFeed: React.FC = () => {
const theme = useTheme()
@ -140,11 +140,11 @@ export const NotificationsFeed: React.FC = () => {
}
}, [])
const renderItem: (note: Note) => JSX.Element = (note) => {
const renderItem: ListRenderItem<Note> = ({ item }) => {
return (
<View style={styles.noteCard} key={note.id}>
<View style={styles.noteCard} key={item.id}>
<NoteCard
note={note}
note={item}
onPressUser={(user) => {
setProfileCardPubKey(user.id)
bottomSheetProfileRef.current?.open()
@ -175,37 +175,43 @@ export const NotificationsFeed: React.FC = () => {
}
}, [])
const ListEmptyComponent = React.useMemo(
() => (
<View style={styles.blank}>
<MaterialCommunityIcons
name='bell-outline'
size={64}
style={styles.center}
color={theme.colors.onPrimaryContainer}
/>
<Text variant='headlineSmall' style={styles.center}>
{t('notificationsFeed.emptyTitle')}
</Text>
<Text variant='bodyMedium' style={styles.center}>
{t('notificationsFeed.emptyDescription')}
</Text>
<Button mode='contained' compact onPress={() => navigate('Send')}>
{t('notificationsFeed.emptyButton')}
</Button>
</View>
),
[],
)
return (
<View style={styles.container}>
{notes && notes.length > 0 ? (
<ScrollView
onScroll={onScroll}
horizontal={false}
showsVerticalScrollIndicator={false}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
>
{notes.map((note) => renderItem(note))}
{notes.length >= 10 && <ActivityIndicator animating={true} />}
</ScrollView>
) : (
<View style={styles.blank}>
<MaterialCommunityIcons
name='bell-outline'
size={64}
style={styles.center}
color={theme.colors.onPrimaryContainer}
/>
<Text variant='headlineSmall' style={styles.center}>
{t('notificationsFeed.emptyTitle')}
</Text>
<Text variant='bodyMedium' style={styles.center}>
{t('notificationsFeed.emptyDescription')}
</Text>
<Button mode='contained' compact onPress={() => navigate('Send')}>
{t('notificationsFeed.emptyButton')}
</Button>
</View>
)}
<FlashList
estimatedItemSize={200}
showsVerticalScrollIndicator={false}
data={notes}
renderItem={renderItem}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
onScroll={onScroll}
refreshing={refreshing}
ListEmptyComponent={ListEmptyComponent}
horizontal={false}
ListFooterComponent={<ActivityIndicator animating={true} />}
/>
<RBSheet ref={bottomSheetProfileRef} closeOnDragDown={true} customStyles={bottomSheetStyles}>
<ProfileCard userPubKey={profileCardPubkey ?? ''} bottomSheetRef={bottomSheetProfileRef} />
</RBSheet>

View File

@ -273,6 +273,7 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
</Surface>
<View style={styles.list}>
<FlashList
estimatedItemSize={200}
showsVerticalScrollIndicator={false}
data={notes}
renderItem={renderItem}

View File

@ -167,6 +167,7 @@ export const ReactionsFeed: React.FC<ReactionsFeedProps> = ({
return (
<View style={styles.list}>
<FlashList
estimatedItemSize={200}
showsVerticalScrollIndicator={false}
data={notes}
renderItem={renderItem}

View File

@ -1,5 +1,5 @@
import React, { useContext, useState } from 'react'
import { ScrollView, StyleSheet, View } from 'react-native'
import { FlatList, ListRenderItem, ScrollView, StyleSheet, View } from 'react-native'
import Clipboard from '@react-native-clipboard/clipboard'
import { useTranslation } from 'react-i18next'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
@ -23,8 +23,7 @@ import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityI
export const RelaysPage: React.FC = () => {
const defaultRelayInput = React.useMemo(() => 'wss://', [])
const { activeRelayItem, desactiveRelayItem, addRelayItem, removeRelayItem, relays } =
useContext(RelayPoolContext)
const { updateRelayItem, addRelayItem, removeRelayItem, relays } = useContext(RelayPoolContext)
const { t } = useTranslation('common')
const theme = useTheme()
const bottomSheetAddRef = React.useRef<RBSheet>(null)
@ -36,7 +35,8 @@ export const RelaysPage: React.FC = () => {
const addRelay: (url: string) => void = (url) => {
addRelayItem({
url,
active: true,
active: 1,
global_feed: 1,
}).then(() => {
setShowNotification('add')
})
@ -51,29 +51,45 @@ export const RelaysPage: React.FC = () => {
}
const activeRelay: (relay: Relay) => void = (relay) => {
activeRelayItem(relay).then(() => {
relay.active = 1
updateRelayItem(relay).then(() => {
setShowNotification('active')
})
}
const desactiveRelay: (relay: Relay) => void = (relay) => {
desactiveRelayItem(relay).then(() => {
relay.active = 0
updateRelayItem(relay).then(() => {
setShowNotification('desactive')
})
}
const activeGlobalFeedRelay: (relay: Relay) => void = (relay) => {
relay.global_feed = 1
updateRelayItem(relay).then(() => {
setShowNotification('globalFeedActive')
})
}
const desactiveGlobalFeedRelay: (relay: Relay) => void = (relay) => {
relay.global_feed = 0
updateRelayItem(relay).then(() => {
setShowNotification('globalFeedActiveUnactive')
})
}
const onPressAddRelay: () => void = () => {
if (
REGEX_SOCKET_LINK.test(addRelayInput) &&
!relays.find((relay) => relay.url === addRelayInput)
) {
bottomSheetAddRef.current?.close()
addRelay(addRelayInput)
setAddRelayInput(defaultRelayInput)
if (REGEX_SOCKET_LINK.test(addRelayInput)) {
if (relays.find((relay) => relay.url === addRelayInput)) {
setShowNotification('alreadyExists')
} else {
addRelay(addRelayInput)
setAddRelayInput(defaultRelayInput)
}
} else {
bottomSheetAddRef.current?.close()
setShowNotification('badFormat')
}
bottomSheetAddRef.current?.close()
}
const rbSheetCustomStyles = React.useMemo(() => {
@ -91,78 +107,87 @@ export const RelaysPage: React.FC = () => {
}
}, [])
const myRelays = relays.filter((relay) => !defaultRelays.includes(relay.url))
const myRelays = relays
.filter((relay) => !defaultRelays.includes(relay.url))
.sort((a, b) => {
if (a.url > b.url) return 1
if (a.url < b.url) return -1
return 0
})
const renderItem: ListRenderItem<Relay> = ({ item, index }) => {
return (
<List.Item
key={index}
title={item.url.split('wss://')[1]?.split('/')[0]}
right={() => (
<>
<Switch
value={item.global_feed !== undefined && item.global_feed > 0}
onValueChange={() =>
item.active ? desactiveGlobalFeedRelay(item) : activeGlobalFeedRelay(item)
}
/>
<Switch
style={styles.switch}
value={item.active !== undefined && item.active > 0}
onValueChange={() => (item.active ? desactiveRelay(item) : activeRelay(item))}
/>
</>
)}
left={() => (
<MaterialCommunityIcons
style={styles.relayColor}
name='circle'
color={relayToColor(item.url)}
/>
)}
onPress={() => {
setSelectedRelay(item)
bottomSheetEditRef.current?.open()
}}
/>
)
}
return (
<View style={styles.container}>
<List.Item
title={t('relaysPage.relayName')}
right={() => (
<>
<Text style={styles.listHeader}>{t('relaysPage.globalFeed')}</Text>
<Text style={styles.listHeader}>{t('relaysPage.active')}</Text>
</>
)}
/>
<ScrollView horizontal={false}>
<Text style={styles.title} variant='titleMedium'>
{t('relaysPage.myList')}
</Text>
{myRelays.length > 0 && (
<>
<Text style={styles.title} variant='titleMedium'>
{t('relaysPage.myList')}
</Text>
{myRelays.length > 0 &&
myRelays.map((relay, index) => {
return (
<List.Item
key={index}
title={relay.url.split('wss://')[1]?.split('/')[0]}
right={() => (
<Switch
value={relay.active !== undefined && relay.active > 0}
onValueChange={() =>
relay.active ? desactiveRelay(relay) : activeRelay(relay)
}
/>
)}
left={() => (
<MaterialCommunityIcons
style={styles.relayColor}
name='circle'
color={relayToColor(relay.url)}
/>
)}
onPress={() => {
setSelectedRelay(relay)
bottomSheetEditRef.current?.open()
}}
/>
)
})}
<FlatList
showsVerticalScrollIndicator={false}
data={myRelays}
renderItem={renderItem}
/>
</>
)}
<Text style={styles.title} variant='titleMedium'>
{t('relaysPage.recommended')}
</Text>
{defaultRelays.map((url, index) => {
const relay = {
url,
active: relays.find((relay) => relay.url === url && relay.active) !== undefined,
}
return (
<List.Item
key={index}
title={url.split('wss://')[1]?.split('/')[0]}
right={() => (
<Switch
value={relay.active}
onValueChange={() => (relay.active ? desactiveRelay(relay) : activeRelay(relay))}
/>
)}
left={() => (
<MaterialCommunityIcons
style={styles.relayColor}
name='circle'
color={relayToColor(relay.url)}
/>
)}
onPress={() => {
setSelectedRelay(relay)
bottomSheetEditRef.current?.open()
}}
/>
)
})}
<FlatList
showsVerticalScrollIndicator={false}
data={defaultRelays.map(
(url) =>
relays.find((relay) => relay.url === url && relay.active && relay.active > 0) ?? {
url,
},
)}
renderItem={renderItem}
style={styles.list}
/>
</ScrollView>
<AnimatedFAB
style={styles.fab}
@ -252,12 +277,10 @@ const styles = StyleSheet.create({
},
container: {
padding: 0,
paddingBottom: 32,
paddingLeft: 16,
},
list: {
padding: 0,
paddingBottom: 45,
paddingBottom: 80,
},
snackbar: {
margin: 16,
@ -266,6 +289,14 @@ const styles = StyleSheet.create({
relayColor: {
paddingTop: 9,
},
switch: {
marginLeft: 32,
},
listHeader: {
paddingRight: 5,
paddingLeft: 16,
textAlign: 'center',
},
fab: {
bottom: 16,
right: 16,

View File

@ -156,6 +156,7 @@ export const RepostsFeed: React.FC<RepostsFeedProps> = ({ navigation, setProfile
return (
<View style={styles.list}>
<FlashList
estimatedItemSize={200}
showsVerticalScrollIndicator={false}
data={notes}
renderItem={renderItem}

View File

@ -2,12 +2,11 @@ import { NativeModules } from 'react-native'
const { RelayPoolModule } = NativeModules
interface RelayPoolInterface {
send: (message: string) => void
send: (message: string, globalFeed: boolean) => void
connect: (pubKey: string, callback: (eventId: string) => void) => void
add: (url: string, callback: () => void) => void
remove: (url: string, callback: () => void) => void
active: (url: string, callback: () => void) => void
desactive: (url: string, callback: () => void) => void
update: (relayUrl: string, active: number, globalfeed: number, callback?: () => void) => void
onEventId: (callback: (eventId: string) => void) => void
}

View File

@ -26,9 +26,12 @@ class RelayPool {
private readonly privateKey?: string
private subscriptions: Record<string, string[]>
private readonly send: (message: object) => void = async (message) => {
private readonly send: (message: object, globalFeed?: boolean) => void = async (
message,
globalFeed,
) => {
const tosend = JSON.stringify(message)
RelayPoolModule.send(tosend)
RelayPoolModule.send(tosend, globalFeed ?? false)
}
public readonly connect: (publicKey: string, onEventId: (eventId: string) => void) => void =
@ -50,18 +53,13 @@ class RelayPool {
RelayPoolModule.remove(relayUrl, callback)
}
public readonly active: (relayUrl: string, callback?: () => void) => void = async (
relayUrl,
callback = () => {},
) => {
RelayPoolModule.active(relayUrl, callback)
}
public readonly desactive: (relayUrl: string, callback?: () => void) => void = async (
relayUrl,
callback = () => {},
) => {
RelayPoolModule.desactive(relayUrl, callback)
public readonly update: (
relayUrl: string,
active: number,
globalfeed: number,
callback?: () => void,
) => void = async (relayUrl, active, globalfeed, callback = () => {}) => {
RelayPoolModule.update(relayUrl, active, globalfeed, callback)
}
public readonly sendEvent: (event: Event) => Promise<Event | null> = async (event) => {
@ -89,7 +87,7 @@ class RelayPool {
if (this.subscriptions[subId]?.includes(id)) {
console.log('Subscription already done!', subId)
} else {
this.send([...['REQ', subId], ...(filters ?? [])])
this.send([...['REQ', subId], ...(filters ?? [])], subId.includes('-global-'))
const newSubscriptions = [...(this.subscriptions[subId] ?? []), id]
this.subscriptions[subId] = newSubscriptions
}