blocked users (#281)

This commit is contained in:
KoalaSat 2023-02-08 16:19:36 +00:00 committed by GitHub
commit e321c66725
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 141 additions and 25 deletions

View File

@ -84,6 +84,18 @@ export const getFollowersCount: (db: QuickSQLiteConnection) => Promise<number> =
return item['COUNT(*)'] ?? 0 return item['COUNT(*)'] ?? 0
} }
export const getBlocked: (db: QuickSQLiteConnection) => Promise<User[]> = async (db) => {
const userQuery = 'SELECT * FROM nostros_users WHERE blocked = 1 ORDER BY created_at DESC'
const resultSet = db.execute(userQuery)
if (resultSet.rows && resultSet.rows.length > 0) {
const items: object[] = getItems(resultSet)
const users: User[] = items.map((object) => databaseToEntity(object))
return users
} else {
return []
}
}
export const getFollowersAndFollowing: (db: QuickSQLiteConnection) => Promise<User[]> = async ( export const getFollowersAndFollowing: (db: QuickSQLiteConnection) => Promise<User[]> = async (
db, db,
) => { ) => {

View File

@ -131,7 +131,8 @@
"keyCopied": "Öffentlichen Schlüssel kopiert", "keyCopied": "Öffentlichen Schlüssel kopiert",
"contactAdded": "Kontakt hinzugefügt", "contactAdded": "Kontakt hinzugefügt",
"addContactError": "Ein Fehler ist aufgetreten.", "addContactError": "Ein Fehler ist aufgetreten.",
"contactRemoved": "Abo wurde entfernt" "contactRemoved": "Abo wurde entfernt",
"contactUnblocked": "Profile unblocked."
}, },
"emptyTitleFollowing": "Du folgst niemandem", "emptyTitleFollowing": "Du folgst niemandem",
"emptyDescriptionFollowing": "Folge anderen, und sieh hier was sie posten", "emptyDescriptionFollowing": "Folge anderen, und sieh hier was sie posten",
@ -145,6 +146,8 @@
"addContactTitle": "Kontakt hinzufügen", "addContactTitle": "Kontakt hinzufügen",
"followers": "({{count}}) folgen mir", "followers": "({{count}}) folgen mir",
"following": "Ich folge ({{count}})", "following": "Ich folge ({{count}})",
"blocked": "Blocked ({{count}})",
"unblock": "Unblock",
"stopFollowing": "Abo stoppen", "stopFollowing": "Abo stoppen",
"follow": "Folgen" "follow": "Folgen"
}, },

View File

@ -131,7 +131,8 @@
"keyCopied": "Public key copied.", "keyCopied": "Public key copied.",
"contactAdded": "Profile followed.", "contactAdded": "Profile followed.",
"addContactError": "There was an error publishing your changes.", "addContactError": "There was an error publishing your changes.",
"contactRemoved": "Profile unfollowed." "contactRemoved": "Profile unfollowed.",
"contactUnblocked": "Profile unblocked."
}, },
"emptyTitleFollowing": "You are not following anyone.", "emptyTitleFollowing": "You are not following anyone.",
"emptyDescriptionFollowing": "Follow other profiles to see content.", "emptyDescriptionFollowing": "Follow other profiles to see content.",
@ -146,7 +147,9 @@
"followers": "Followers ({{count}})", "followers": "Followers ({{count}})",
"following": "Following ({{count}})", "following": "Following ({{count}})",
"stopFollowing": "Stop Following", "stopFollowing": "Stop Following",
"follow": "Follow" "follow": "Follow",
"blocked": "Blocked ({{count}})",
"unblock": "Unblock"
}, },
"aboutPage": { "aboutPage": {
"gitHub": "GitHub", "gitHub": "GitHub",

View File

@ -131,7 +131,8 @@
"keyCopied": "Clave pública copiada.", "keyCopied": "Clave pública copiada.",
"contactAdded": "Perfil seguido.", "contactAdded": "Perfil seguido.",
"addContactError": "Se ha producido un error al publicar tus cambios.", "addContactError": "Se ha producido un error al publicar tus cambios.",
"contactRemoved": "Has dejado de seguir a un perfil" "contactRemoved": "Has dejado de seguir a un perfil",
"contactUnblocked": "Perfil desbloqueado."
}, },
"emptyTitleFollowing": "No sigues a nadie", "emptyTitleFollowing": "No sigues a nadie",
"emptyDescriptionFollowing": "Sigue otros perfiles para ver contenido aquí.", "emptyDescriptionFollowing": "Sigue otros perfiles para ver contenido aquí.",
@ -145,6 +146,8 @@
"addContactTitle": "Añadir contacto", "addContactTitle": "Añadir contacto",
"followers": "Seguidores ({{count}})", "followers": "Seguidores ({{count}})",
"following": "Siguiendo ({{count}})", "following": "Siguiendo ({{count}})",
"blocked": "Bloqueados ({{count}})",
"unblock": "Desbloquear",
"stopFollowing": "Dejar de seguir", "stopFollowing": "Dejar de seguir",
"follow": "Seguir" "follow": "Seguir"
}, },
@ -188,9 +191,9 @@
}, },
"relaysPage": { "relaysPage": {
"resilienceTitle": "Resilience (experimental)", "resilienceTitle": "Resilience (experimental)",
"resilienceDescription": "The nostr protocol provides a good amount of tools to build a desentralized network, but unlike other desentralized protocols such as Bitcoin, it's not inherent by defaul on his usage.\n\nTo ahcieve such goal, clients and relays should activally colaborate. Nostros resiliense mode tries to, first, expose these concerns to their users, and secondly, start experimening with new pattern and way to help on network' resilency.\n\nFunctional implementation will soon endup appearing on Nostros by default.", "resilienceDescription": "The Nostr protocol provides a good number of tools to build a decentralized network, but unlike other decentralized protocols such as Bitcoin, it is not inherently bound to its use. To achieve the goal of a decentralized network, clients (e.g., Nostros) and relays (content servers) should actively cooperate. The Nostros resilience mode seeks first to raise awareness of this among users, and second to test new patterns and methods to improve the resilience of the network.\nA working implementation for this will soon be applied in Nostros.",
"resilienceCategories": "Resilience exposure", "resilienceCategories": "Resilience exposure",
"resilienceCategoriesDescription": "A first approach Nostros is testing is randomly calculate (based on received contats' events) what are the most resilient relays. It tries to avoid both relays with too much events (centralization) and relays with almost no events (battery draining).\n\nThe goal is to generate a list of 5 relays and cover all contacts. But if this is not possible, it will search among other realys, warning to the user about it.", "resilienceCategoriesDescription": "An initial approach that Nostros is testing is to randomly calculate (based on received contact events) which relays are most balanced. This tries to avoid both relays with too many events (centralization) and relays with almost no events (battery discharge).\n\nThe goal is to create a list of 5 relays that cover all contacts. However, if this is not possible, Nostros will search among other relays and warn the user about it.",
"centralized": "Centralizado", "centralized": "Centralizado",
"small": "Pequeño", "small": "Pequeño",
"contacts": "Nº contactos", "contacts": "Nº contactos",

View File

@ -132,7 +132,8 @@
"keyCopied": "Clé publique copiée.", "keyCopied": "Clé publique copiée.",
"contactAdded": "Profil suivi.", "contactAdded": "Profil suivi.",
"addContactError": "Une erreur s'est produite lors de vos modifications.", "addContactError": "Une erreur s'est produite lors de vos modifications.",
"contactRemoved": "Vous ne suivez plus ce profil" "contactRemoved": "Vous ne suivez plus ce profil",
"contactUnblocked": "Profile unblocked."
}, },
"emptyTitleFollowing": "Vous ne suivez personne", "emptyTitleFollowing": "Vous ne suivez personne",
"emptyDescriptionFollowing": "Suivez les autres profils pour voir le contenu ici.", "emptyDescriptionFollowing": "Suivez les autres profils pour voir le contenu ici.",
@ -146,6 +147,8 @@
"addContactTitle": "Ajouter un contact", "addContactTitle": "Ajouter un contact",
"followers": "Abonnés ({{count}})", "followers": "Abonnés ({{count}})",
"following": "Abonnements ({{count}})", "following": "Abonnements ({{count}})",
"blocked": "Blocked ({{count}})",
"unblock": "Unblock",
"stopFollowing": "Cessez de suivre", "stopFollowing": "Cessez de suivre",
"follow": "Suivre" "follow": "Suivre"
}, },
@ -189,9 +192,9 @@
}, },
"relaysPage": { "relaysPage": {
"resilienceTitle": "Resilience (experimental)", "resilienceTitle": "Resilience (experimental)",
"resilienceDescription": "The nostr protocol provides a good amount of tools to build a desentralized network, but unlike other desentralized protocols such as Bitcoin, it's not inherent by defaul on his usage.\n\nTo ahcieve such goal, clients and relays should activally colaborate. Nostros resiliense mode tries to, first, expose these concerns to their users, and secondly, start experimening with new pattern and way to help on network' resilency.\n\nFunctional implementation will soon endup appearing on Nostros by default.", "resilienceDescription": "The Nostr protocol provides a good number of tools to build a decentralized network, but unlike other decentralized protocols such as Bitcoin, it is not inherently bound to its use. To achieve the goal of a decentralized network, clients (e.g., Nostros) and relays (content servers) should actively cooperate. The Nostros resilience mode seeks first to raise awareness of this among users, and second to test new patterns and methods to improve the resilience of the network.\nA working implementation for this will soon be applied in Nostros.",
"resilienceCategories": "Resilience exposure", "resilienceCategories": "Resilience exposure",
"resilienceCategoriesDescription": "A first approach Nostros is testing is randomly calculate (based on received contats' events) what are the most resilient relays. It tries to avoid both relays with too much events (centralization) and relays with almost no events (battery draining).\n\nThe goal is to generate a list of 5 relays and cover all contacts. But if this is not possible, it will search among other realys, warning to the user about it.", "resilienceCategoriesDescription": "An initial approach that Nostros is testing is to randomly calculate (based on received contact events) which relays are most balanced. This tries to avoid both relays with too many events (centralization) and relays with almost no events (battery discharge).\n\nThe goal is to create a list of 5 relays that cover all contacts. However, if this is not possible, Nostros will search among other relays and warn the user about it.",
"centralized": "Centralized", "centralized": "Centralized",
"small": "Small", "small": "Small",
"contacts": "# Contacts", "contacts": "# Contacts",

View File

@ -131,7 +131,8 @@
"keyCopied": "Публичный ключ скопирован.", "keyCopied": "Публичный ключ скопирован.",
"contactAdded": "Вы подписались.", "contactAdded": "Вы подписались.",
"addContactError": "Произошла ошибка при публикации изменений.", "addContactError": "Произошла ошибка при публикации изменений.",
"contactRemoved": "Вы отписались." "contactRemoved": "Вы отписались.",
"contactUnblocked": "Profile unblocked."
}, },
"emptyTitleFollowing": "Вы ни на кого не подписаны", "emptyTitleFollowing": "Вы ни на кого не подписаны",
"emptyDescriptionFollowing": "Подпишитесь на другие профили, чтобы увидеть их заметки", "emptyDescriptionFollowing": "Подпишитесь на другие профили, чтобы увидеть их заметки",
@ -145,6 +146,8 @@
"addContactTitle": "Add contact", "addContactTitle": "Add contact",
"followers": "Подписчики ({{count}})", "followers": "Подписчики ({{count}})",
"following": "Подписки ({{count}})", "following": "Подписки ({{count}})",
"blocked": "Blocked ({{count}})",
"unblock": "Unblock",
"stopFollowing": "Отписаться", "stopFollowing": "Отписаться",
"follow": "Подписаться" "follow": "Подписаться"
}, },
@ -188,9 +191,9 @@
}, },
"relaysPage": { "relaysPage": {
"resilienceTitle": "Resilience (experimental)", "resilienceTitle": "Resilience (experimental)",
"resilienceDescription": "The nostr protocol provides a good amount of tools to build a desentralized network, but unlike other desentralized protocols such as Bitcoin, it's not inherent by defaul on his usage.\n\nTo ahcieve such goal, clients and relays should activally colaborate. Nostros resiliense mode tries to, first, expose these concerns to their users, and secondly, start experimening with new pattern and way to help on network' resilency.\n\nFunctional implementation will soon endup appearing on Nostros by default.", "resilienceDescription": "The Nostr protocol provides a good number of tools to build a decentralized network, but unlike other decentralized protocols such as Bitcoin, it is not inherently bound to its use. To achieve the goal of a decentralized network, clients (e.g., Nostros) and relays (content servers) should actively cooperate. The Nostros resilience mode seeks first to raise awareness of this among users, and second to test new patterns and methods to improve the resilience of the network.\nA working implementation for this will soon be applied in Nostros.",
"resilienceCategories": "Resilience exposure", "resilienceCategories": "Resilience exposure",
"resilienceCategoriesDescription": "A first approach Nostros is testing is randomly calculate (based on received contats' events) what are the most resilient relays. It tries to avoid both relays with too much events (centralization) and relays with almost no events (battery draining).\n\nThe goal is to generate a list of 5 relays and cover all contacts. But if this is not possible, it will search among other realys, warning to the user about it.", "resilienceCategoriesDescription": "An initial approach that Nostros is testing is to randomly calculate (based on received contact events) which relays are most balanced. This tries to avoid both relays with too many events (centralization) and relays with almost no events (battery discharge).\n\nThe goal is to create a list of 5 relays that cover all contacts. However, if this is not possible, Nostros will search among other relays and warn the user about it.",
"centralized": "Centralized", "centralized": "Centralized",
"small": "Small", "small": "Small",
"contacts": "# Contacts", "contacts": "# Contacts",

View File

@ -129,7 +129,8 @@
"keyCopied": "已复制公钥", "keyCopied": "已复制公钥",
"contactAdded": "已关注", "contactAdded": "已关注",
"addContactError": "发布更新时发生了错误", "addContactError": "发布更新时发生了错误",
"contactRemoved": "已取消关注" "contactRemoved": "已取消关注",
"contactUnblocked": "Profile unblocked."
}, },
"emptyTitleFollowing": "您还没有关注任何用户", "emptyTitleFollowing": "您还没有关注任何用户",
"emptyDescriptionFollowing": "关注一些用户以查看内容", "emptyDescriptionFollowing": "关注一些用户以查看内容",
@ -144,7 +145,9 @@
"followers": "关注者 ({{count}})", "followers": "关注者 ({{count}})",
"following": "关注中 ({{count}})", "following": "关注中 ({{count}})",
"stopFollowing": "正在关注", "stopFollowing": "正在关注",
"follow": "回关" "follow": "回关",
"blocked": "Blocked ({{count}})",
"unblock": "Unblock"
}, },
"aboutPage": { "aboutPage": {
"gitHub": "GitHub", "gitHub": "GitHub",

View File

@ -1,19 +1,14 @@
import React, { useContext, useEffect, useState } from 'react' import React, { useContext, useEffect, useState } from 'react'
import { import { Dimensions, NativeScrollEvent, NativeSyntheticEvent, StyleSheet, View } from 'react-native'
ActivityIndicator,
Dimensions,
NativeScrollEvent,
NativeSyntheticEvent,
StyleSheet,
View,
} from 'react-native'
import Clipboard from '@react-native-clipboard/clipboard' import Clipboard from '@react-native-clipboard/clipboard'
import { AppContext } from '../../Contexts/AppContext' import { AppContext } from '../../Contexts/AppContext'
import { Kind } from 'nostr-tools' import { Kind } from 'nostr-tools'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { FlashList, ListRenderItem } from '@shopify/flash-list' import { FlashList, ListRenderItem } from '@shopify/flash-list'
import { import {
getBlocked,
getFollowersAndFollowing, getFollowersAndFollowing,
updateUserBlock,
updateUserContact, updateUserContact,
User, User,
} from '../../Functions/DatabaseFunctions/Users' } from '../../Functions/DatabaseFunctions/Users'
@ -51,6 +46,7 @@ export const ContactsFeed: React.FC = () => {
// State // State
const [followers, setFollowers] = useState<User[]>([]) const [followers, setFollowers] = useState<User[]>([])
const [following, setFollowing] = useState<User[]>([]) const [following, setFollowing] = useState<User[]>([])
const [blocked, setBlocked] = useState<User[]>([])
const [contactInput, setContactInput] = useState<string>() const [contactInput, setContactInput] = useState<string>()
const [isAddingContact, setIsAddingContact] = useState<boolean>(false) const [isAddingContact, setIsAddingContact] = useState<boolean>(false)
const [showNotification, setShowNotification] = useState<undefined | string>() const [showNotification, setShowNotification] = useState<undefined | string>()
@ -72,6 +68,9 @@ export const ContactsFeed: React.FC = () => {
const loadUsers: () => void = () => { const loadUsers: () => void = () => {
if (database && publicKey) { if (database && publicKey) {
getBlocked(database).then((results) => {
if (results) setBlocked(results)
})
getFollowersAndFollowing(database).then((results) => { getFollowersAndFollowing(database).then((results) => {
const followers: User[] = [] const followers: User[] = []
const following: User[] = [] const following: User[] = []
@ -169,6 +168,15 @@ export const ContactsFeed: React.FC = () => {
} }
} }
const unblock: (user: User) => void = (user) => {
if (relayPool && database && publicKey) {
updateUserBlock(user.id, database, false).then(() => {
setShowNotification('contactUnblocked')
loadUsers()
})
}
}
const renderContactItem: ListRenderItem<User> = ({ index, item }) => { const renderContactItem: ListRenderItem<User> = ({ index, item }) => {
return ( return (
<TouchableRipple <TouchableRipple
@ -199,6 +207,34 @@ export const ContactsFeed: React.FC = () => {
) )
} }
const renderBlockedItem: ListRenderItem<User> = ({ index, item }) => {
return (
<TouchableRipple
onPress={() => {
setDisplayUserDrawer(item.id)
bottomSheetProfileRef.current?.open()
}}
>
<View key={item.id} style={styles.contactRow}>
<View style={styles.profileData}>
<ProfileData
username={item?.name}
publicKey={getNpub(item.id)}
validNip05={item?.valid_nip05}
nip05={item?.nip05}
lud06={item?.lnurl}
picture={item?.picture}
avatarSize={40}
/>
</View>
<View>
<Button onPress={() => unblock(item)}>{t('contactsFeed.unblock')}</Button>
</View>
</View>
</TouchableRipple>
)
}
const bottomSheetStyles = React.useMemo(() => { const bottomSheetStyles = React.useMemo(() => {
return { return {
container: { container: {
@ -250,7 +286,6 @@ export const ContactsFeed: React.FC = () => {
onScroll={onScroll} onScroll={onScroll}
ListEmptyComponent={ListEmptyComponentFollowing} ListEmptyComponent={ListEmptyComponentFollowing}
horizontal={false} horizontal={false}
ListFooterComponent={<ActivityIndicator animating={true} />}
/> />
</View> </View>
) )
@ -293,7 +328,37 @@ export const ContactsFeed: React.FC = () => {
onScroll={onScroll} onScroll={onScroll}
ItemSeparatorComponent={Divider} ItemSeparatorComponent={Divider}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
ListFooterComponent={<ActivityIndicator animating={true} />} />
</View>
)
const ListEmptyComponentBlocked = (
<View style={styles.blankNoButton}>
<MaterialCommunityIcons
name='account-group-outline'
size={64}
style={styles.center}
color={theme.colors.onPrimaryContainer}
/>
<Text variant='headlineSmall' style={styles.center}>
{t('contactsFeed.emptyTitleBlocked')}
</Text>
<Text variant='bodyMedium' style={styles.center}>
{t('contactsFeed.emptyDescriptionBlocked')}
</Text>
</View>
)
const Blocked: JSX.Element = (
<View style={styles.container}>
<FlashList
estimatedItemSize={71}
showsVerticalScrollIndicator={false}
data={blocked.slice(0, pageSize)}
renderItem={renderBlockedItem}
onScroll={onScroll}
ListEmptyComponent={ListEmptyComponentBlocked}
horizontal={false}
/> />
</View> </View>
) )
@ -301,6 +366,7 @@ export const ContactsFeed: React.FC = () => {
const renderScene: Record<string, JSX.Element> = { const renderScene: Record<string, JSX.Element> = {
following: Following, following: Following,
followers: Followers, followers: Followers,
blocked: Blocked,
} }
return ( return (
@ -334,6 +400,20 @@ export const ContactsFeed: React.FC = () => {
</Text> </Text>
</TouchableRipple> </TouchableRipple>
</View> </View>
<View
style={[
styles.tab,
tabKey === 'blocked'
? { ...styles.tabActive, borderBottomColor: theme.colors.primary }
: {},
]}
>
<TouchableRipple style={styles.textWrapper} onPress={() => setTabKey('blocked')}>
<Text style={styles.tabText}>
{t('contactsFeed.blocked', { count: blocked.length })}
</Text>
</TouchableRipple>
</View>
</View> </View>
{renderScene[tabKey]} {renderScene[tabKey]}
{privateKey && ( {privateKey && (
@ -477,6 +557,12 @@ const styles = StyleSheet.create({
marginTop: 75, marginTop: 75,
padding: 16, padding: 16,
}, },
blankNoButton: {
justifyContent: 'space-between',
height: 192,
marginTop: 75,
padding: 16,
},
tabLabel: { tabLabel: {
margin: 8, margin: 8,
}, },

View File

@ -238,7 +238,7 @@ export const SendPage: React.FC<SendPageProps> = ({ route }) => {
</View> </View>
<View style={styles.actions}> <View style={styles.actions}>
{userSuggestions.length > 0 ? ( {userSuggestions.length > 0 ? (
<View style={styles.contactsList}> <View style={[styles.contactsList, { backgroundColor: theme.colors.background }]}>
{userSuggestions.map((user, index) => renderContactItem(user, index))} {userSuggestions.map((user, index) => renderContactItem(user, index))}
</View> </View>
) : ( ) : (