Better Home

This commit is contained in:
KoalaSat 2022-12-23 19:19:52 +01:00
parent 7b3bf3e021
commit 4b0d1e3d06
No known key found for this signature in database
GPG Key ID: 2F7F61C6146AB157
14 changed files with 305 additions and 97 deletions

View File

@ -80,22 +80,22 @@ public class Event {
protected String getReplyEventId() {
JSONArray eTags = filterTags("e");
String mainEventId = null;
String replyEventId = null;
try {
for (int i = 0; i < eTags.length(); ++i) {
JSONArray tag = eTags.getJSONArray(i);
if (tag.length() > 3 && tag.getString(3).equals("reply")) {
mainEventId = tag.getString(1);
replyEventId = tag.getString(1);
}
}
if (mainEventId == null && eTags.length() > 0) {
mainEventId = eTags.getJSONArray(eTags.length() - 1).getString(1);
if (replyEventId == null && eTags.length() > 0) {
replyEventId = eTags.getJSONArray(eTags.length() - 1).getString(1);
}
} catch (JSONException e) {
e.printStackTrace();
}
return mainEventId;
return replyEventId;
}
protected String saveFollower(String pubKey) {

View File

@ -77,14 +77,16 @@ public class DatabaseModule {
String query = "SELECT url FROM nostros_relays;";
@SuppressLint("Recycle") Cursor cursor = database.rawQuery(query, new String[] {});
if (cursor.getCount() > 0) {
for (int i = 1; i < cursor.getCount(); i++) {
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
try {
String relayUrl = cursor.getString(i);
String relayUrl = cursor.getString(0);
Relay relay = new Relay(relayUrl, this, reactContext);
relayList.add(relay);
} catch (IOException e) {
Log.d("WebSocket", e.toString());
}
cursor.moveToNext();
}
}
return relayList;

View File

@ -25,7 +25,6 @@ import {
} from '../../Functions/RelayFunctions/DirectMessages'
import Avatar from '../Avatar'
import Icon from 'react-native-vector-icons/FontAwesome5'
import { decrypt } from 'nostr-tools/nip04'
import { useTranslation } from 'react-i18next'
import { username } from '../../Functions/RelayFunctions/Users'
@ -100,16 +99,6 @@ export const DirectMessagesPage: React.FC = () => {
const otherPubKey = getOtherPubKey(message, publicKey)
const user: User = users?.find((user) => user.id === otherPubKey) ?? { id: otherPubKey }
let decryptedContent = ''
try {
decryptedContent = decrypt(privateKey, otherPubKey, message.content ?? '')
} catch {
return <View key={message.id}></View>
}
const content =
decryptedContent.length > 35 ? `${decryptedContent.substring(0, 35)}...` : decryptedContent
return (
<Card
@ -121,8 +110,7 @@ export const DirectMessagesPage: React.FC = () => {
<Avatar name={user.name} src={user.picture} pubKey={user.id} />
</Layout>
<Layout style={styles.content} level='2'>
<Text>{username(user)}</Text>
<Text appearance='hint'>{content}</Text>
<Text category='h6'>{username(user)}</Text>
</Layout>
<Layout style={styles.layout}>
{!message.read ? (
@ -155,6 +143,7 @@ export const DirectMessagesPage: React.FC = () => {
content: {
flex: 6,
backgroundColor: 'transparent',
justifyContent: 'center',
},
actionContainer: {
marginTop: 30,

View File

@ -1,15 +1,15 @@
import { Card, Layout, Spinner, useTheme } from '@ui-kitten/components'
import { Card, Layout, Text, useTheme } from '@ui-kitten/components'
import React, { useContext, useEffect, useState } from 'react'
import { t } from 'i18next'
import {
NativeScrollEvent,
NativeSyntheticEvent,
ScrollView,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native'
import { AppContext } from '../../Contexts/AppContext'
import { getNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
import { getMainNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
import NoteCard from '../NoteCard'
import Icon from 'react-native-vector-icons/FontAwesome5'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
@ -17,7 +17,6 @@ import { EventKind } from '../../lib/nostr/Events'
import { getReplyEventId } from '../../Functions/RelayFunctions/Events'
import { getUsers, User } from '../../Functions/DatabaseFunctions/Users'
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
import Loading from '../Loading'
import { RelayFilters } from '../../lib/nostr/RelayPool/intex'
export const HomePage: React.FC = () => {
@ -37,33 +36,40 @@ export const HomePage: React.FC = () => {
}
}
const subscribeNotes: (users: User[], past?: boolean) => void = (users, past) => {
const subscribeNotes: (users: User[], past?: boolean) => void = async (users, past) => {
if (!database || !publicKey || users.length === 0) return
const lastNotes: Note[] = await getMainNotes(database, publicKey, 1)
const lastNote: Note = lastNotes[0]
const message: RelayFilters = {
kinds: [EventKind.textNote, EventKind.recommendServer],
authors: users.map((user) => user.id),
limit: pageSize,
}
if (lastNote) {
message.since = lastNote.created_at
} else {
message.limit = pageSize
}
relayPool?.subscribe('main-channel', message)
}
const loadNotes: () => void = () => {
if (database && publicKey) {
getNotes(database, { contacts: true, includeIds: [publicKey], limit: pageSize }).then(
(notes) => {
setNotes(notes)
const missingDataNotes = notes
.filter((note) => !note.picture || note.picture === '')
.map((note) => note.pubkey)
if (missingDataNotes.length > 0) {
relayPool?.subscribe('main-channel', {
kinds: [EventKind.meta],
authors: missingDataNotes,
})
}
},
)
getMainNotes(database, publicKey, pageSize).then((notes) => {
setNotes(notes)
const missingDataNotes = notes
.filter((note) => !note.picture || note.picture === '')
.map((note) => note.pubkey)
if (missingDataNotes.length > 0) {
relayPool?.subscribe('main-channel', {
kinds: [EventKind.meta],
authors: missingDataNotes,
})
}
})
}
}
@ -100,7 +106,7 @@ export const HomePage: React.FC = () => {
const itemCard: (note: Note) => JSX.Element = (note) => {
return (
<Card onPress={() => onPress(note)} key={note.id ?? ''}>
<NoteCard note={note} />
<NoteCard note={note} onlyContactsReplies={true} showReplies={true} />
</Card>
)
}
@ -119,13 +125,10 @@ export const HomePage: React.FC = () => {
width: 32,
height: 32,
},
loadingBottom: {
flex: 1,
flexDirection: 'row',
empty: {
height: 64,
justifyContent: 'center',
alignItems: 'center',
flexWrap: 'wrap',
padding: 12,
},
})
@ -135,12 +138,11 @@ export const HomePage: React.FC = () => {
{notes && notes.length > 0 ? (
<ScrollView onScroll={onScroll} horizontal={false}>
{notes.map((note) => itemCard(note))}
<View style={styles.loadingBottom}>
<Spinner size='tiny' />
</View>
</ScrollView>
) : (
<Loading />
<Layout style={styles.empty} level='3'>
<Text>{t('homePage.noContacts')}</Text>
</Layout>
)}
</Layout>
{privateKey && (

View File

@ -1,16 +1,28 @@
import React from 'react'
import { Layout, Text } from '@ui-kitten/components'
import { StyleSheet } from 'react-native'
import { Layout, Text, useTheme } from '@ui-kitten/components'
import { Linking, StyleSheet, TouchableOpacity } from 'react-native'
import Loading from '../Loading'
import Logger from './Logger'
import Icon from 'react-native-vector-icons/FontAwesome5'
export const LandingPage: React.FC = () => {
const theme = useTheme()
const onPressQuestion: () => void = () => {
Linking.openURL('https://usenostr.org')
}
const styles = StyleSheet.create({
tab: {
height: '100%',
alignItems: 'center',
justifyContent: 'center',
},
info: {
alignItems: 'flex-end',
padding: 12,
paddingBottom: -12,
},
svg: {
height: 340,
width: 340,
@ -24,13 +36,20 @@ export const LandingPage: React.FC = () => {
})
return (
<Layout style={styles.tab}>
<Layout style={styles.svg}>
<Loading />
<>
<Layout style={styles.info}>
<TouchableOpacity onPress={onPressQuestion}>
<Icon name='question' size={24} color={theme['text-basic-color']} />
</TouchableOpacity>
</Layout>
<Text style={styles.title}>NOSTROS</Text>
<Logger />
</Layout>
<Layout style={styles.tab}>
<Layout style={styles.svg}>
<Loading />
</Layout>
<Text style={styles.title}>NOSTROS</Text>
<Logger />
</Layout>
</>
)
}

View File

@ -1,6 +1,6 @@
import React, { useContext, useEffect, useState } from 'react'
import { Button, Layout, Text, useTheme } from '@ui-kitten/components'
import { Note } from '../../Functions/DatabaseFunctions/Notes'
import { Button, Divider, Layout, Text, useTheme } from '@ui-kitten/components'
import { getNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
import { StyleSheet, TouchableOpacity } from 'react-native'
import Markdown from 'react-native-markdown-display'
import { EventKind } from '../../lib/nostr/Events'
@ -9,32 +9,56 @@ import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
import { AppContext } from '../../Contexts/AppContext'
import { showMessage } from 'react-native-flash-message'
import { t } from 'i18next'
import { getReplyEventId } from '../../Functions/RelayFunctions/Events'
import { getDirectReplies, getReplyEventId } from '../../Functions/RelayFunctions/Events'
import moment from 'moment'
import { populateRelay } from '../../Functions/RelayFunctions'
import Avatar from '../Avatar'
import { markdownIt, markdownStyle } from '../../Constants/AppConstants'
import { searchRelays } from '../../Functions/DatabaseFunctions/Relays'
import { RelayFilters } from '../../lib/nostr/RelayPool/intex'
interface NoteCardProps {
note: Note
showReplies?: boolean
onlyContactsReplies?: boolean
}
export const NoteCard: React.FC<NoteCardProps> = ({ note }) => {
export const NoteCard: React.FC<NoteCardProps> = ({
note,
showReplies = false,
onlyContactsReplies = false,
}) => {
const theme = useTheme()
const { relayPool, publicKey } = useContext(RelayPoolContext)
const { database, goToPage } = useContext(AppContext)
const [relayAdded, setRelayAdded] = useState<boolean>(false)
const [replies, setReplies] = useState<Note[]>([])
useEffect(() => {
if (database) {
if (database && note) {
searchRelays(note.content, database).then((result) => {
setRelayAdded(result.length > 0)
})
if (showReplies) {
getNotes(database, {
filters: { reply_event_id: note?.id ?? '' },
contacts: onlyContactsReplies,
}).then((notes) => {
const rootReplies = getDirectReplies(note, notes) as Note[]
setReplies(rootReplies)
if (rootReplies.length > 0) {
const message: RelayFilters = {
kinds: [EventKind.meta],
authors: [...rootReplies.map((reply) => reply.pubkey), note.pubkey],
}
relayPool?.subscribe('main-channel', message)
}
})
}
}
}, [database])
const textNote: () => JSX.Element = () => {
const textNote: (note: Note) => JSX.Element = (note) => {
return (
<>
<Layout style={styles.profile} level='2'>
@ -74,7 +98,7 @@ export const NoteCard: React.FC<NoteCardProps> = ({ note }) => {
)
}
const relayNote: () => JSX.Element = () => {
const relayNote: (note: Note) => JSX.Element = (note) => {
const relayName = note.content.split('wss://')[1]?.split('/')[0]
const addRelayItem: () => void = () => {
@ -123,6 +147,15 @@ export const NoteCard: React.FC<NoteCardProps> = ({ note }) => {
layout: {
flexDirection: 'row',
backgroundColor: 'transparent',
paddingBottom: 16,
},
replies: {
flexDirection: 'row',
backgroundColor: 'transparent',
paddingTop: 16,
},
divider: {
paddingTop: 16,
},
profile: {
flex: 1,
@ -168,9 +201,23 @@ export const NoteCard: React.FC<NoteCardProps> = ({ note }) => {
return (
note && (
<Layout style={styles.layout} level='2'>
{note.kind === EventKind.recommendServer ? relayNote() : textNote()}
</Layout>
<>
<Layout style={styles.layout} level='2'>
{note.kind === EventKind.recommendServer ? relayNote(note) : textNote(note)}
</Layout>
{replies.length > 0 ? (
<>
<Divider />
{replies.map((reply) => (
<Layout style={styles.replies} level='3' key={reply.id}>
{textNote(reply)}
</Layout>
))}
</>
) : (
<></>
)}
</>
)
)
}

View File

@ -1,4 +1,4 @@
import { Button, Card, Layout, Spinner, TopNavigation, useTheme } from '@ui-kitten/components'
import { Button, Card, Layout, TopNavigation, useTheme } from '@ui-kitten/components'
import React, { useContext, useEffect, useState } from 'react'
import { AppContext } from '../../Contexts/AppContext'
import { getNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
@ -6,7 +6,7 @@ import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
import Icon from 'react-native-vector-icons/FontAwesome5'
import NoteCard from '../NoteCard'
import { EventKind } from '../../lib/nostr/Events'
import { Clipboard, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
import { Clipboard, ScrollView, StyleSheet, Text, TouchableOpacity } from 'react-native'
import Loading from '../Loading'
import { getDirectReplies, getReplyEventId } from '../../Functions/RelayFunctions/Events'
import { RelayFilters } from '../../lib/nostr/RelayPool/intex'
@ -155,14 +155,6 @@ export const NotePage: React.FC = () => {
loading: {
maxHeight: 160,
},
loadingBottom: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
flexWrap: 'wrap',
padding: 12,
},
})
return (
@ -181,9 +173,6 @@ export const NotePage: React.FC = () => {
{note ? (
<ScrollView horizontal={false}>
{[note, ...(replies ?? [])].map((note) => itemCard(note))}
<View style={styles.loadingBottom}>
<Spinner size='tiny' />
</View>
</ScrollView>
) : (
<Loading style={styles.loading} />

View File

@ -8,7 +8,6 @@ import {
ScrollView,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native'
import { AppContext } from '../../Contexts/AppContext'
import { getNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
@ -231,20 +230,12 @@ export const ProfilePage: React.FC = () => {
marginTop: 16,
flexDirection: 'row',
},
loadingBottom: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
flexWrap: 'wrap',
padding: 12,
},
})
const itemCard: (note: Note) => JSX.Element = (note) => {
return (
<Card onPress={() => onPressNote(note)} key={note.id ?? ''}>
<NoteCard note={note} />
<NoteCard note={note} onlyContactsReplies={true} />
</Card>
)
}
@ -315,9 +306,6 @@ export const ProfilePage: React.FC = () => {
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
>
{notes.map((note) => itemCard(note))}
<View style={styles.loadingBottom}>
<Spinner size='tiny' />
</View>
</ScrollView>
) : (
<Loading />

View File

@ -43,7 +43,7 @@ export const RelaysPage: React.FC = () => {
)
const addRelayItem: (url: string) => void = async (url) => {
if (relayPool && database && publicKey && (relays?.length ?? 0) < 2) {
if (relayPool && database && publicKey) {
setLoading(true)
relayPool.add(url, () => {
showMessage({
@ -81,7 +81,7 @@ export const RelaysPage: React.FC = () => {
) : (
<Button
status='success'
disabled={loading || (relays?.length ?? 0) > 0}
disabled={loading}
onPress={() => addRelayItem(relay.url)}
accessoryLeft={<Icon name='plus' size={16} color={theme['text-basic-color']} solid />}
/>

View File

@ -12,6 +12,29 @@ const databaseToEntity: (object: any) => Note = (object) => {
return object as Note
}
export const getMainNotes: (
db: QuickSQLiteConnection,
pubKey: string,
limit: number,
) => Promise<Note[]> = async (db, pubKey, limit) => {
const notesQuery = `
SELECT
nostros_notes.*, nostros_users.name, nostros_users.picture, nostros_users.contact FROM nostros_notes
LEFT JOIN
nostros_users ON nostros_users.id = nostros_notes.pubkey
WHERE (nostros_users.contact = 1 OR nostros_users.id = '${pubKey}')
AND nostros_notes.main_event_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 getNotes: (
db: QuickSQLiteConnection,
options: {

View File

@ -15,7 +15,8 @@
},
"homePage": {
"search": "Search",
"send": "Send"
"send": "Send",
"noContacts": "You have no contacts yet"
},
"logger": {
"randomKeyGenerator": {

72
frontend/Locales/es.json Normal file
View File

@ -0,0 +1,72 @@
{
"common": {
"landing": {
"connect": "Conectar",
"connecting": "Conectando",
"loadingContacts": "Cargando Contactos",
"loadingEvents": "Cargando Eventos",
"ready": "Listo",
"privateKey": "Clave Privada",
"publicKey": "Clave Pública"
},
"navigationBar": {
"home": "Home",
"profile": "Perfil"
},
"homePage": {
"search": "Buscar",
"send": "Enviar",
"noContacts": "Aún no tienes contactos"
},
"logger": {
"randomKeyGenerator": {
"message": "Tu clave privada ha sido copiada al porta-papeles",
"description": "Guardala en un ligar seguro para recuperar tu usuario"
}
},
"profilePage": {
"search": "Burcar",
"send": "Enviar",
"profileNotCreated": "Perfil aún no creado"
},
"relaysPage": {
"title": "Relays"
},
"notePage": {
"reply": "Responder"
},
"configPage": {
"title": "Configuraciónn",
"logout": "Borrar Claves",
"relays": "Relays",
"publicKey": "Clave Pública",
"privateKey": "Clave Privada"
},
"direcMessagesPage": {
"sendMessage": {
"placeholder": "Clave Pública",
"send": "Abrir Conversación",
"contacts": "Contactos"
}
},
"contactsPage": {
"following": "Siguiendo",
"followers": "Seguidores",
"add": "Añadir Usuario",
"addContact": {
"placeholder": "Clave Pública",
"add": "Añadir Usuario"
}
},
"sendPage": {
"title": "Enviar Nota",
"placeholder": "Di algo al mundo...",
"send": "Enviar",
"reply": "Enviar respuesta"
},
"alerts": {
"relayAdded": "Relay Agregado",
"relayRemoved": "Relay Eliminado"
}
}
}

72
frontend/Locales/ru.json Normal file
View File

@ -0,0 +1,72 @@
{
"common": {
"landing": {
"connect": "Подключить",
"connecting": "Подключение",
"loadingContacts": "Загрузка контактов",
"loadingEvents": "Загрузка событий",
"ready": "Готова",
"privateKey": "Приватный ключ",
"publicKey": "Публичный ключ"
},
"navigationBar": {
"home": "Главная страницы",
"profile": "Профиль"
},
"homePage": {
"search": "Поиск",
"send": "Отправить",
"noContacts": "У Вас пока нет контактов"
},
"logger": {
"randomKeyGenerator": {
"message": "Ваш приватный ключ был скопирован",
"description": "Сохраните его в надеждном месте, чтобы восстановить доступ к акканунту"
}
},
"profilePage": {
"search": "Поиск",
"send": "Отправить",
"profileNotCreated": "Профиль не создан"
},
"relaysPage": {
"title": "Все реле"
},
"notePage": {
"reply": "Ответить"
},
"configPage": {
"title": "Настройки",
"logout": "Удалить ключи",
"relays": "Реле",
"publicKey": "Публичный ключ",
"privateKey": "Приватный ключ"
},
"direcMessagesPage": {
"sendMessage": {
"placeholder": "Публичный ключ",
"send": "Открыть диалог",
"contacts": "Контакты"
}
},
"contactsPage": {
"following": "Подписки",
"followers": "Подписчики",
"add": "Добавить контакт",
"addContact": {
"placeholder": "Публичный ключ",
"add": "Добавить контакт"
}
},
"sendPage": {
"title": "Опубликовать заметку",
"placeholder": "Поделитесь чем-нибудь с миром",
"send": "Опубликовать",
"reply": "Ответить"
},
"alerts": {
"relayAdded": "Реле добавлено",
"relayRemoved": "Реле удалено"
}
}
}

View File

@ -1,12 +1,16 @@
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import en from './Locales/en.json'
import es from './Locales/es.json'
import ru from './Locales/ru.json'
i18n.use(initReactI18next).init({
compatibilityJSON: 'v3',
fallbackLng: 'en',
resources: {
en,
es,
ru,
},
ns: ['common'],
defaultNS: 'common',