Followers list

This commit is contained in:
KoalaSat 2022-11-06 19:57:52 +01:00
parent acb36bcc94
commit f83f6d82da
No known key found for this signature in database
GPG Key ID: 2F7F61C6146AB157
10 changed files with 151 additions and 64 deletions

View File

@ -1,4 +1,4 @@
import { Button, Card, Input, Layout, Modal, useTheme } from '@ui-kitten/components'
import { Button, Card, Input, Layout, Modal, Tab, TabBar, useTheme } from '@ui-kitten/components'
import React, { useCallback, useContext, useEffect, useState } from 'react'
import { RefreshControl, ScrollView, StyleSheet, TouchableOpacity } from 'react-native'
import { AppContext } from '../../Contexts/AppContext'
@ -6,9 +6,10 @@ import Icon from 'react-native-vector-icons/FontAwesome5'
import { Event, EventKind } from '../../lib/nostr/Events'
import { useTranslation } from 'react-i18next'
import {
addContact,
getUsers,
insertUserContact,
insertUserPets,
updateUserContact,
updateUserFollower,
User,
} from '../../Functions/DatabaseFunctions/Users'
import UserCard from '../UserCard'
@ -25,6 +26,7 @@ export const ContactsPage: React.FC = () => {
const [refreshing, setRefreshing] = useState(true)
const [showAddContact, setShowAddContact] = useState<boolean>(false)
const [contactInput, setContactInput] = useState<string>()
const [selectedTab, setSelectedTab] = useState(0)
const { t } = useTranslation('common')
@ -33,15 +35,23 @@ export const ContactsPage: React.FC = () => {
}, [lastEventId])
useEffect(() => {
setUsers([])
subscribeContacts()
}, [])
}, [selectedTab])
const loadUsers: () => void = () => {
if (database && publicKey) {
setRefreshing(true)
setTimeout(() => setRefreshing(false), 5000)
if (selectedTab === 0) {
getUsers(database, { contacts: true }).then((results) => {
if (results) setUsers(results)
})
} else {
getUsers(database, { followers: true }).then((results) => {
if (results) setUsers(results)
})
}
}
}
@ -49,22 +59,36 @@ export const ContactsPage: React.FC = () => {
relayPool?.unsubscribeAll()
relayPool?.on('event', 'contacts', (relay: Relay, _subId?: string, event?: Event) => {
console.log('CONTACTS PAGE EVENT =======>', relay.url, event)
if (database && event?.id && event.kind === EventKind.petNames) {
insertUserContact(event, database).finally(() => setLastEventId(event?.id ?? ''))
if (database && event?.id && publicKey && event.kind === EventKind.petNames) {
if (event.pubkey === publicKey) {
insertUserPets(event, database).finally(() => setLastEventId(event?.id ?? ''))
relayPool?.removeOn('event', 'contacts')
} else {
const isFollower = event.tags.some((tag) => tag[1] === publicKey)
updateUserFollower(event.pubkey, database, isFollower).finally(() =>
setLastEventId(event?.id ?? ''),
)
}
}
})
if (publicKey) {
if (selectedTab === 0) {
relayPool?.subscribe('main-channel', {
kinds: [EventKind.petNames],
authors: [publicKey],
})
} else if (selectedTab === 1) {
relayPool?.subscribe('main-channel', {
kinds: [EventKind.petNames],
'#p': [publicKey],
})
}
}
}
const onPressAddContact: () => void = () => {
if (contactInput && relayPool && database && publicKey) {
addContact(contactInput, database).then(() => {
updateUserContact(contactInput, database, true).then(() => {
populatePets(relayPool, database, publicKey)
setShowAddContact(false)
loadUsers()
@ -76,6 +100,12 @@ export const ContactsPage: React.FC = () => {
setRefreshing(true)
relayPool?.unsubscribeAll()
subscribeContacts()
if (users && users?.length > 0) {
relayPool?.subscribe('main-channel', {
kinds: [EventKind.meta],
authors: users?.map((user) => user.id),
})
}
}, [])
const styles = StyleSheet.create({
@ -103,10 +133,21 @@ export const ContactsPage: React.FC = () => {
backdrop: {
backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
topBar: {
height: 64,
},
})
return (
<>
<TabBar
style={styles.topBar}
selectedIndex={selectedTab}
onSelect={(index) => setSelectedTab(index)}
>
<Tab title={t('contactsPage.following')} />
<Tab title={t('contactsPage.followers')} />
</TabBar>
<Layout style={styles.container} level='3'>
<ScrollView
horizontal={false}

View File

@ -7,7 +7,7 @@ import { tagToUser } from '../../../Functions/RelayFunctions/Users'
import Relay from '../../../lib/nostr/Relay'
import { Event, EventKind } from '../../../lib/nostr/Events'
import { AppContext } from '../../../Contexts/AppContext'
import { insertUserContact, insertUserMeta } from '../../../Functions/DatabaseFunctions/Users'
import { insertUserPets, insertUserMeta } from '../../../Functions/DatabaseFunctions/Users'
import SInfo from 'react-native-sensitive-info'
import Icon from 'react-native-vector-icons/FontAwesome5'
import { generateRandomKey, getPublickey } from '../../../lib/nostr/Bip'
@ -75,7 +75,7 @@ export const Logger: React.FC = () => {
if (database) {
if (event.tags.length > 0) {
setStatus(2)
insertUserContact(event, database).then(() => {
insertUserPets(event, database).then(() => {
requestUserData(event)
})
} else {

View File

@ -22,7 +22,13 @@ import { AppContext } from '../../Contexts/AppContext'
import { getNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
import NoteCard from '../NoteCard'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
import { getUser, removeContact, addContact, User } from '../../Functions/DatabaseFunctions/Users'
import {
getUser,
removeContact,
User,
updateUserFollower,
updateUserContact,
} from '../../Functions/DatabaseFunctions/Users'
import { EventKind, Event } from '../../lib/nostr/Events'
import Relay, { RelayFilters } from '../../lib/nostr/Relay'
import Icon from 'react-native-vector-icons/FontAwesome5'
@ -35,7 +41,8 @@ import Avatar from '../Avatar'
export const ProfilePage: React.FC = () => {
const { database, page, goToPage, goBack } = useContext(AppContext)
const { publicKey, privateKey, lastEventId, relayPool } = useContext(RelayPoolContext)
const { publicKey, privateKey, lastEventId, relayPool, setLastEventId } =
useContext(RelayPoolContext)
const theme = useTheme()
const initialPageSize = 10
const [notes, setNotes] = useState<Note[]>()
@ -114,6 +121,12 @@ export const ProfilePage: React.FC = () => {
setTimeout(() => setRefreshing(false), 5000)
storeEvent(event, database)
subscribeNotes()
if (publicKey && event.pubkey !== publicKey) {
const isFollower = event.tags.some((tag) => tag[1] === publicKey)
updateUserFollower(event.pubkey, database, isFollower).finally(() =>
setLastEventId(event?.id ?? ''),
)
}
}
}
}
@ -153,7 +166,7 @@ export const ProfilePage: React.FC = () => {
const addAuthor: () => void = () => {
if (relayPool && database && publicKey) {
addContact(userId, database).then(() => {
updateUserContact(userId, database, true).then(() => {
populatePets(relayPool, database, publicKey)
setIsContact(true)
})

View File

@ -22,7 +22,7 @@ export const RelaysPage: React.FC = () => {
const { goBack, database } = useContext(AppContext)
const { relayPool, publicKey, setRelayPool } = useContext(RelayPoolContext)
const { t } = useTranslation('common')
const [relays, setRelays] = useState<Relay[]>()
const [relays, setRelays] = useState<Relay[]>([])
const [loading, setLoading] = useState<boolean>(false)
const loadRelays: () => void = () => {
@ -157,7 +157,7 @@ export const RelaysPage: React.FC = () => {
/>
<Layout style={styles.actionContainer} level='2'>
<ScrollView refreshControl={<RefreshControl refreshing={loading} />}>
{relays?.length ? (
{relays ? (
[...relays, ...defaultList()].map((item, index) => {
return renderItem(item, index)
})

View File

@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react'
import { QuickSQLiteConnection } from 'react-native-quick-sqlite'
import { initDatabase } from '../Functions/DatabaseFunctions'
import { createInitDatabase } from '../Functions/DatabaseFunctions/Migrations'
import { createInitDatabase, runMigrations } from '../Functions/DatabaseFunctions/Migrations'
import FlashMessage from 'react-native-flash-message'
import SInfo from 'react-native-sensitive-info'
import { BackHandler } from 'react-native'
@ -50,6 +50,7 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E
setLoadingDb(false)
})
} else {
runMigrations(db)
setLoadingDb(false)
}
})

View File

@ -8,7 +8,6 @@ import { getRelays, addRelay } from '../Functions/DatabaseFunctions/Relays'
import { showMessage } from 'react-native-flash-message'
import SInfo from 'react-native-sensitive-info'
import { getPublickey } from '../lib/nostr/Bip'
import { pickRandomItems } from '../Functions/NativeFunctions'
import { defaultRelays } from '../Constants/RelayConstants'
export interface RelayPoolContextProps {
@ -58,10 +57,10 @@ export const RelayPoolContextProvider = ({
initRelayPool.add(relay.url)
})
} else {
pickRandomItems(defaultRelays, 2).forEach((relayUrl) => {
initRelayPool.add(relayUrl)
addRelay({ url: relayUrl }, database)
})
// pickRandomItems(defaultRelays, 1).forEach((relayUrl) => {
initRelayPool.add(defaultRelays[4])
addRelay({ url: defaultRelays[4] }, database)
// })
}
initRelayPool?.on(

View File

@ -1,7 +1,8 @@
import { QuickSQLiteConnection } from 'react-native-quick-sqlite'
import { simpleExecute } from '..'
import { ColumnMetadata, QuickSQLiteConnection } from 'react-native-quick-sqlite'
import { dropTables, simpleExecute } from '..'
export const createInitDatabase: (db: QuickSQLiteConnection) => Promise<void> = async (db) => {
dropTables(db)
await simpleExecute(
`
CREATE TABLE IF NOT EXISTS nostros_notes(
@ -40,4 +41,13 @@ export const createInitDatabase: (db: QuickSQLiteConnection) => Promise<void> =
`,
db,
)
await runMigrations(db)
}
export const runMigrations: (db: QuickSQLiteConnection) => Promise<void> = async (db) => {
const { metadata } = db.execute('SELECT * FROM nostros_users;')
// v0.1.8.1 alpha
if (!metadata?.some((meta: ColumnMetadata) => meta.columnName === 'follower')) {
await simpleExecute('ALTER TABLE nostros_users ADD COLUMN follower BOOLEAN DEFAULT FALSE;', db)
}
}

View File

@ -81,7 +81,7 @@ export const getNotes: (
} else {
notesQuery += 'WHERE '
}
notesQuery += 'nostros_users.contact = TRUE '
notesQuery += 'nostros_users.contact = 1 '
}
if (includeIds) {
if (Object.keys(filters).length > 0 || contacts) {

View File

@ -10,6 +10,7 @@ export interface User {
picture?: string
about?: string
contact?: boolean
follower?: boolean
}
const databaseToEntity: (object: object) => User = (object) => {
@ -32,9 +33,9 @@ export const insertUserMeta: (
const query = `
INSERT OR REPLACE INTO nostros_users
(id, name, picture, about, main_relay, contact)
(id, name, picture, about, main_relay, contact, follower)
VALUES
(?,?,?,?,?, (SELECT contact FROM nostros_users WHERE id = ?));
(?,?,?,?,?,(SELECT contact FROM nostros_users WHERE id = ?),(SELECT follower FROM nostros_users WHERE id = ?));
`
const queryParams = [
id,
@ -43,15 +44,36 @@ export const insertUserMeta: (
about.split("'").join("''"),
mainRelay.split("'").join("''"),
id,
id,
]
return await db.executeAsync(query, queryParams)
} else {
return null
}
}
export const insertUserContact: (
export const updateUserFollower: (
userId: string,
db: QuickSQLiteConnection,
follower: boolean,
) => Promise<QueryResult | null> = async (userId, db, follower) => {
const userQuery = `UPDATE nostros_users SET follower = ? WHERE id = ?`
await addUser(userId, db)
return db.execute(userQuery, [follower ? 1 : 0, userId])
}
export const updateUserContact: (
userId: string,
db: QuickSQLiteConnection,
contact: boolean,
) => Promise<QueryResult | null> = async (userId, db, contact) => {
const userQuery = `UPDATE nostros_users SET contact = ? WHERE id = ?`
await addUser(userId, db)
return db.execute(userQuery, [contact ? 1 : 0, userId])
}
export const insertUserPets: (
event: Event,
db: QuickSQLiteConnection,
) => Promise<User[] | null> = async (event, db) => {
@ -60,7 +82,7 @@ export const insertUserContact: (
if (valid && event.kind === EventKind.petNames) {
const users: User[] = event.tags.map((tag) => tagToUser(tag))
users.map(async (user) => {
return await addContact(user.id, db)
return await updateUserContact(user.id, db, true)
})
return users
} else {
@ -88,51 +110,50 @@ export const removeContact: (
pubkey: string,
db: QuickSQLiteConnection,
) => Promise<QueryResult> = async (pubkey, db) => {
const userQuery = `UPDATE nostros_users SET contact = FALSE WHERE id = ?`
const userQuery = `UPDATE nostros_users SET contact = 0 WHERE id = ?`
return db.execute(userQuery, [pubkey])
}
export const addContact: (
pubKey: string,
db: QuickSQLiteConnection,
) => Promise<QueryResult> = async (pubKey, db) => {
export const addUser: (pubKey: string, db: QuickSQLiteConnection) => Promise<QueryResult> = async (
pubKey,
db,
) => {
const query = `
INSERT INTO nostros_users (id, contact)
VALUES (?, ?)
ON CONFLICT (id) DO
UPDATE SET contact=excluded.contact;
INSERT OR IGNORE INTO nostros_users (id) VALUES (?)
`
return db.execute(query, [pubKey, '1'])
return db.execute(query, [pubKey])
}
export const getUsers: (
db: QuickSQLiteConnection,
options: { exludeIds?: string[]; contacts?: boolean; includeIds?: string[] },
) => Promise<User[]> = async (db, { exludeIds, contacts, includeIds }) => {
options: { exludeIds?: string[]; contacts?: boolean; followers?: boolean; includeIds?: string[] },
) => Promise<User[]> = async (db, { exludeIds, contacts, followers, includeIds }) => {
let userQuery = 'SELECT * FROM nostros_users '
if (contacts) {
userQuery += 'WHERE contact = TRUE '
}
const filters = []
if (exludeIds && exludeIds.length > 0) {
if (!contacts) {
userQuery += 'WHERE '
} else {
userQuery += 'AND '
}
userQuery += `id NOT IN ('${exludeIds.join("', '")}') `
if (contacts) {
filters.push('contact = 1')
}
if (includeIds && includeIds.length > 0) {
if (!contacts && !exludeIds) {
userQuery += 'WHERE '
} else {
userQuery += 'OR '
filters.push(`id IN ('${includeIds.join("', '")}')`)
}
if (followers) {
filters.push('follower = 1')
}
if (filters.length > 0) {
userQuery += `WHERE ${filters.join(' OR ')} `
if (exludeIds && exludeIds.length > 0) {
userQuery += `AND id NOT IN ('${exludeIds.join("', '")}') `
}
} else {
if (exludeIds && exludeIds.length > 0) {
userQuery += `WHERE id NOT IN ('${exludeIds.join("', '")}') `
}
userQuery += `id IN ('${includeIds.join("', '")}') `
}
userQuery += 'ORDER BY name,id'

View File

@ -42,6 +42,8 @@
"privateKey": "Private Key"
},
"contactsPage": {
"following": "Following",
"followers": "Followers",
"add": "Add User",
"addContact": {
"placeholder": "Public Key",