Better Landing

This commit is contained in:
KoalaSat 2022-12-28 12:47:21 +01:00
parent e81aebc0bd
commit 228b41a2ac
No known key found for this signature in database
GPG Key ID: 2F7F61C6146AB157
15 changed files with 228 additions and 107 deletions

View File

@ -4,6 +4,7 @@ import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
@ -202,7 +203,7 @@ public class Event {
protected void saveUserMeta(SQLiteDatabase database) throws JSONException {
JSONObject userContent = new JSONObject(content);
String query = "SELECT * FROM nostros_users WHERE id = ?";
String query = "SELECT created_at FROM nostros_users WHERE id = ?";
@SuppressLint("Recycle") Cursor cursor = database.rawQuery(query, new String[] {pubkey});
ContentValues values = new ContentValues();
@ -211,10 +212,11 @@ public class Event {
values.put("about", userContent.optString("about"));
values.put("lnurl", userContent.optString("lnurl"));
values.put("main_relay", userContent.optString("main_relay"));
values.put("created_at", created_at);
if (cursor.getCount() == 0) {
values.put("id", pubkey);
database.insert("nostros_users", null, values);
} else {
} else if (cursor.moveToFirst() && created_at > cursor.getInt(0)) {
String whereClause = "id = ?";
String[] whereArgs = new String[] {
this.pubkey
@ -233,7 +235,7 @@ public class Event {
ContentValues values = new ContentValues();
values.put("id", petId);
values.put("name", tag.getString(3));
values.put("contact", true);
values.put("contact", true);
database.insert("nostros_users", null, values);
}
}

View File

@ -65,6 +65,9 @@ public class DatabaseModule {
try {
database.execSQL("ALTER TABLE nostros_users ADD COLUMN lnurl TEXT;");
} catch (SQLException e) { }
try {
database.execSQL("ALTER TABLE nostros_users ADD COLUMN created_at INT DEFAULT 0;");
} catch (SQLException e) { }
}
public void saveEvent(JSONObject data, String userPubKey) throws JSONException {

View File

@ -11,6 +11,7 @@ import com.nostros.classes.Relay;
import java.io.IOException;
import java.util.List;
import java.util.ListIterator;
public class RelayPoolModule extends ReactContextBaseJavaModule {
protected List<Relay> relays;
@ -48,13 +49,16 @@ public class RelayPoolModule extends ReactContextBaseJavaModule {
@ReactMethod
public void remove(String url, Callback callback) {
for (Relay relay : relays) {
if (relay.url.equals(url)) {
ListIterator<Relay> iterator = relays.listIterator();
while(iterator.hasNext()){
Relay relay = iterator.next();
if(url.equals(relay.url)){
relay.disconnect();
relays.remove(relay);
iterator.remove();
database.destroyRelay(relay);
}
}
callback.invoke();
}
@ -64,6 +68,7 @@ public class RelayPoolModule extends ReactContextBaseJavaModule {
relays = database.getRelays(context);
if (relays.isEmpty()) {
add("wss://relay.damus.io");
add("wss://nostr-relay.wlvs.space");
}
for (Relay relay : relays) {
try {

View File

@ -1,4 +1,4 @@
import { Card, Layout, Text, useTheme } from '@ui-kitten/components'
import { Button, Card, Layout, Text, useTheme } from '@ui-kitten/components'
import React, { useCallback, useContext, useEffect, useState } from 'react'
import { t } from 'i18next'
import {
@ -46,7 +46,7 @@ export const HomePage: React.FC = () => {
const message: RelayFilters = {
kinds: [EventKind.textNote, EventKind.recommendServer],
authors: users.map((user) => user.id)
authors: users.map((user) => user.id),
}
if (lastNote && lastNotes.length > 0 && !past) {
@ -65,7 +65,7 @@ export const HomePage: React.FC = () => {
setRefreshing(false)
relayPool?.subscribe('main-channel', {
kinds: [EventKind.meta],
authors: notes.map((note) => note.pubkey)
authors: notes.map((note) => note.pubkey),
})
})
}
@ -132,6 +132,11 @@ export const HomePage: React.FC = () => {
height: 32,
},
empty: {
height: 128,
justifyContent: 'center',
alignItems: 'center',
},
noContacts: {
height: 64,
justifyContent: 'center',
alignItems: 'center',
@ -142,12 +147,27 @@ export const HomePage: React.FC = () => {
<>
<Layout style={styles.container} level='3'>
{notes.length > 0 ? (
<ScrollView onScroll={onScroll} horizontal={false} refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}>
<ScrollView
onScroll={onScroll}
horizontal={false}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
>
{notes.map((note) => itemCard(note))}
</ScrollView>
) : (
<Layout style={styles.empty} level='3'>
<Text>{t('homePage.noContacts')}</Text>
<Layout style={styles.noContacts} level='3'>
<Text>{t('homePage.noContacts')}</Text>
</Layout>
<Button
onPress={() => goToPage('config')}
status='warning'
accessoryLeft={
<Icon name='address-book' size={16} color={theme['text-basic-color']} solid />
}
>
{t('homePage.addContacts')}
</Button>
</Layout>
)}
</Layout>

View File

@ -0,0 +1,104 @@
import React, { useContext, useEffect, useState } from 'react'
import { Button, Layout, Text, useTheme } from '@ui-kitten/components'
import { StyleSheet } from 'react-native'
import { RelayPoolContext } from '../../../Contexts/RelayPoolContext'
import { EventKind } from '../../../lib/nostr/Events'
import { AppContext } from '../../../Contexts/AppContext'
import { getUser, getUsers, User } from '../../../Functions/DatabaseFunctions/Users'
import Icon from 'react-native-vector-icons/FontAwesome5'
import { useTranslation } from 'react-i18next'
export const Loader: React.FC = () => {
const { goToPage, loadingDb, database } = useContext(AppContext)
const { publicKey, relayPool, lastEventId, loadingRelayPool } = useContext(RelayPoolContext)
const theme = useTheme()
const { t } = useTranslation('common')
const [profileFound, setProfileFound] = useState<boolean>(false)
const [contactsCount, setContactsCount] = useState<number>()
useEffect(() => {
if (!loadingRelayPool && !loadingDb && publicKey) {
relayPool?.subscribe('main-channel', {
kinds: [EventKind.petNames, EventKind.meta, EventKind.directMessage],
authors: [publicKey],
})
}
}, [loadingRelayPool, publicKey, loadingDb])
useEffect(() => {
loadPets()
loadProfile()
}, [lastEventId])
const loadPets: () => void = () => {
if (database) {
getUsers(database, { contacts: true }).then((results) => {
setContactsCount(results.length)
if (results && results.length > 0) {
relayPool?.subscribe('main-channel', {
kinds: [EventKind.meta],
authors: results.map((user: User) => user.id),
})
}
})
}
}
const loadProfile: () => void = () => {
if (database && publicKey) {
getUser(publicKey, database).then((result) => {
if (result) {
setProfileFound(true)
}
})
}
}
const styles = StyleSheet.create({
container: {
padding: 32,
},
text: {
marginVertical: 10,
padding: 32,
alignItems: 'center',
justifyContent: 'center',
},
action: {
backgroundColor: 'transparent',
marginTop: 12,
},
})
return (
<>
<Layout style={styles.text}>
<Text>{profileFound ? t('loader.profileFound') : t('loader.searchingProfile')}</Text>
<Text>{`${t('loader.searchingContacts')} ${contactsCount}`}</Text>
</Layout>
<Layout>
<Text>{t('loader.help1')}</Text>
<Text>{t('loader.help2')}</Text>
</Layout>
<Layout style={styles.action}>
<Button
onPress={() => goToPage('relays')}
status='warning'
accessoryLeft={<Icon name='server' size={16} color={theme['text-basic-color']} solid />}
>
{t('loader.relays')}
</Button>
</Layout>
<Layout style={styles.action}>
<Button
onPress={() => goToPage('home')}
accessoryLeft={<Icon name='home' size={16} color={theme['text-basic-color']} solid />}
>
{t('loader.home')}
</Button>
</Layout>
</>
)
}
export default Loader

View File

@ -1,70 +1,26 @@
import React, { useContext, useEffect, useState } from 'react'
import React, { useContext, useState } from 'react'
import { Button, Input, Layout, useTheme } from '@ui-kitten/components'
import { Clipboard, StyleSheet } from 'react-native'
import { RelayPoolContext } from '../../../Contexts/RelayPoolContext'
import { useTranslation } from 'react-i18next'
import { EventKind } from '../../../lib/nostr/Events'
import { AppContext } from '../../../Contexts/AppContext'
import SInfo from 'react-native-sensitive-info'
import Icon from 'react-native-vector-icons/FontAwesome5'
import { generateRandomKey, getPublickey } from '../../../lib/nostr/Bip'
import { generateRandomKey } from '../../../lib/nostr/Bip'
import { showMessage } from 'react-native-flash-message'
import { getUsers, User } from '../../../Functions/DatabaseFunctions/Users'
export const Logger: React.FC = () => {
const { goToPage, loadingDb, database } = useContext(AppContext)
const { privateKey, publicKey, relayPool, loadingRelayPool, setPrivateKey, setPublicKey } =
useContext(RelayPoolContext)
const { publicKey, setPrivateKey, setPublicKey } = useContext(RelayPoolContext)
const { t } = useTranslation('common')
const theme = useTheme()
const [loading, setLoading] = useState<boolean>(false)
const [status, setStatus] = useState<number>(0)
const [isPrivate, setIsPrivate] = useState<boolean>(true)
const [inputValue, setInputValue] = useState<string>('')
useEffect(() => {
if (loading) {
setStatus(1)
if (isPrivate) {
setPrivateKey(inputValue)
const publicKey: string = getPublickey(inputValue)
setPublicKey(publicKey)
SInfo.setItem('privateKey', inputValue, {})
SInfo.setItem('publicKey', publicKey, {})
} else {
setPublicKey(inputValue)
SInfo.setItem('publicKey', inputValue, {})
}
}
}, [loading])
useEffect(() => {
if (!loadingRelayPool && !loadingDb && publicKey) {
relayPool?.subscribe('main-channel', {
kinds: [EventKind.petNames, EventKind.meta],
authors: [publicKey],
})
setTimeout(loadPets, 4000)
}
}, [loadingRelayPool, publicKey, loadingDb])
const onPress: () => void = () => {
if (inputValue && inputValue !== '') {
setLoading(true)
}
}
const loadPets: () => void = () => {
if (database) {
getUsers(database, { contacts: true }).then((results) => {
if (results && results.length > 0) {
relayPool?.subscribe('main-channel', {
kinds: [EventKind.textNote, EventKind.recommendServer, EventKind.meta],
authors: results.map((user: User) => user.id),
})
}
setTimeout(() => goToPage('home', true), 5000)
})
if (isPrivate) {
setPrivateKey(inputValue)
} else {
setPublicKey(inputValue)
}
}
}
@ -95,11 +51,6 @@ export const Logger: React.FC = () => {
)
}
const statusName: { [status: number]: string } = {
0: t('landing.connect'),
1: t('landing.connecting'),
2: t('landing.ready'),
}
const styles = StyleSheet.create({
inputsContainer: {
flexDirection: 'row',
@ -130,7 +81,7 @@ export const Logger: React.FC = () => {
const label: string = isPrivate ? t('landing.privateKey') : t('landing.publicKey')
return !privateKey || !publicKey || status !== 0 ? (
return (
<>
<Layout style={styles.inputsContainer}>
<Layout style={styles.input}>
@ -139,7 +90,7 @@ export const Logger: React.FC = () => {
label={label}
onChangeText={setInputValue}
value={inputValue}
disabled={loading}
disabled={publicKey !== undefined}
accessoryRight={randomKeyGenerator}
/>
</Layout>
@ -151,20 +102,18 @@ export const Logger: React.FC = () => {
setIsPrivate(!isPrivate)
setInputValue('')
}}
disabled={loading}
disabled={publicKey !== undefined}
accessoryLeft={keyButton}
status={isPrivate ? 'warning' : 'default'}
/>
</Layout>
<Layout style={styles.buttonRight}>
<Button onPress={onPress} disabled={loading}>
{statusName[status]}
<Button onPress={onPress} disabled={publicKey !== undefined}>
{t('landing.connect')}
</Button>
</Layout>
</Layout>
</>
) : (
<></>
)
}

View File

@ -1,11 +1,14 @@
import React from 'react'
import React, { useContext } from 'react'
import { Layout, Text, useTheme } from '@ui-kitten/components'
import { Linking, StyleSheet, TouchableOpacity } from 'react-native'
import Loading from '../Loading'
import Logger from './Logger'
import Loader from './Loader'
import Icon from 'react-native-vector-icons/FontAwesome5'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
export const LandingPage: React.FC = () => {
const { publicKey } = useContext(RelayPoolContext)
const theme = useTheme()
const onPressQuestion: () => void = () => {
@ -37,6 +40,10 @@ export const LandingPage: React.FC = () => {
fontFamily: 'SpaceGrotesk-Bold',
fontSize: 45,
},
action: {
backgroundColor: 'transparent',
marginTop: 30,
},
})
return (
@ -50,8 +57,14 @@ export const LandingPage: React.FC = () => {
<Layout style={styles.svg}>
<Loading />
</Layout>
<Text style={styles.title}>NOSTROS</Text>
<Logger />
{!publicKey ? (
<>
<Text style={styles.title}>NOSTROS</Text>
<Logger />
</>
) : (
<Loader />
)}
</Layout>
</>
)

View File

@ -4,12 +4,11 @@ import {
Layout,
List,
ListItem,
Spinner,
Toggle,
TopNavigation,
useTheme,
} from '@ui-kitten/components'
import React, { useContext, useEffect, useRef, useState } from 'react'
import React, { useContext, useRef, useState } from 'react'
import { StyleSheet } from 'react-native'
import { AppContext } from '../../Contexts/AppContext'
import Icon from 'react-native-vector-icons/FontAwesome5'
@ -31,16 +30,11 @@ export const SendPage: React.FC = () => {
const scrollViewRef = useRef<Input>()
const [content, setContent] = useState<string>('')
const [contentWarning, setContentWarning] = useState<boolean>(false)
const [sending, setSending] = useState<boolean>(false)
const [userSuggestions, setUserSuggestions] = useState<User[]>([])
const [userMentions, setUserMentions] = useState<User[]>([])
const breadcrump = page.split('%')
const eventId = breadcrump[breadcrump.length - 1].split('#')[1]
useEffect(() => {
relayPool?.unsubscribeAll()
}, [])
const styles = StyleSheet.create({
container: {
flex: 1,
@ -55,10 +49,6 @@ export const SendPage: React.FC = () => {
},
})
const onPressBack: () => void = () => {
goBack()
}
const onChangeText: (text: string) => void = (text) => {
const match = text.match(/@(\S*)$/)
if (database && match && match[1].length > 0) {
@ -110,8 +100,7 @@ export const SendPage: React.FC = () => {
pubkey: publicKey,
tags,
}
relayPool?.sendEvent(event)
setSending(true)
relayPool?.sendEvent(event).then(() => goBack())
})
}
}
@ -151,7 +140,7 @@ export const SendPage: React.FC = () => {
const renderBackAction = (): JSX.Element => (
<Button
accessoryRight={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
onPress={onPressBack}
onPress={() => goBack()}
appearance='ghost'
/>
)
@ -179,13 +168,7 @@ export const SendPage: React.FC = () => {
/>
</Layout>
<Layout style={styles.button}>
<Button
onPress={onPressSend}
disabled={sending}
accessoryLeft={sending ? <Spinner size='small' /> : <></>}
>
{t('sendPage.send')}
</Button>
<Button onPress={onPressSend}>{t('sendPage.send')}</Button>
</Layout>
<Layout style={styles.button} level='2'>
<Toggle checked={contentWarning} onChange={setContentWarning}>

View File

@ -1,11 +1,12 @@
import React, { useContext, useEffect, useState } from 'react'
import ParsedText from 'react-native-parsed-text'
import { Text, useTheme } from '@ui-kitten/components'
import { useTheme } from '@ui-kitten/components'
import { Event } from '../../lib/nostr/Events'
import { Linking, StyleSheet } from 'react-native'
import { AppContext } from '../../Contexts/AppContext'
import { getUser } from '../../Functions/DatabaseFunctions/Users'
import { formatPubKey } from '../../Functions/RelayFunctions/Users'
import moment from 'moment'
interface TextBoxProps {
note: Event
@ -44,6 +45,7 @@ export const TextBox: React.FC<TextBoxProps> = ({ note }) => {
const pudKey = note.tags[mentionIndex][1]
if (database) {
getUser(pudKey, database).then((user) => {
setLoadedUsers(moment().unix())
setUserNames((prev) => {
if (user?.name) prev[mentionIndex] = `@${user.name}`
return prev

View File

@ -1,4 +1,5 @@
export const defaultRelays = [
'wss://nostr-relay.wlvs.space',
'wss://nostr-pub.wellorder.net',
'wss://nostr-relay.wlvs.space',
'wss://nostr.onsats.org',

View File

@ -74,6 +74,7 @@ export const RelayPoolContextProvider = ({
useEffect(() => {
if (publicKey && publicKey !== '') {
SInfo.setItem('publicKey', publicKey, {})
if (!loadingRelayPool && page !== 'landing') {
goToPage('home', true)
} else {
@ -82,6 +83,14 @@ export const RelayPoolContextProvider = ({
}
}, [publicKey, loadingRelayPool])
useEffect(() => {
if (privateKey && privateKey !== '') {
SInfo.setItem('privateKey', privateKey, {})
const publicKey: string = getPublickey(privateKey)
setPublicKey(publicKey)
}
}, [privateKey])
useEffect(() => {
if (!loadingDb) {
SInfo.getItem('privateKey', {}).then((privateResult) => {

View File

@ -22,7 +22,7 @@ export const getMainNotes: (
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}')
WHERE (nostros_users.contact = 1 OR nostros_notes.pubkey = '${pubKey}')
AND nostros_notes.main_event_id IS NULL
ORDER BY created_at DESC
LIMIT ${limit}

View File

@ -16,7 +16,8 @@
"homePage": {
"search": "Search",
"send": "Send",
"noContacts": "You have no contacts yet"
"noContacts": "You have no contacts yet",
"addContacts": "Add contacts"
},
"logger": {
"randomKeyGenerator": {
@ -33,6 +34,15 @@
"relaysPage": {
"title": "Relays"
},
"loader": {
"searchingProfile": "Searching your profile",
"profileFound": "Profile found",
"searchingContacts": "Searching contacts:",
"help1": "Problems loading your data?",
"help2": "Try connecting to other relays.",
"relays": "Relays",
"home": "Go to Home"
},
"notePage": {
"reply": "Reply"
},
@ -44,7 +54,7 @@
"privateKey": "Private key",
"username": "Username",
"picture": "Picture URL",
"lnurl": "LNURL",
"lnurl": "LNURL / Lightning address",
"about": "About",
"publish": "Publish"
},

View File

@ -16,7 +16,8 @@
"homePage": {
"search": "Buscar",
"send": "Enviar",
"noContacts": "Aún no tienes contactos"
"noContacts": "Aún no tienes contactos",
"addContacts": "Añadir contactos"
},
"logger": {
"randomKeyGenerator": {
@ -36,6 +37,15 @@
"notePage": {
"reply": "Responder"
},
"loader": {
"searchingProfile": "Buscando tu perfil",
"profileFound": "Perfil encontrado",
"searchingContacts": "Buscando tus contactos:",
"help1": "¿Tienes problemas encontrando tus datos?",
"help2": "Intenta contectarte a otros Relays.",
"relays": "Relays",
"home": "Ir a Home"
},
"configPage": {
"title": "Configuración",
"logout": "Borrar claves",
@ -44,7 +54,7 @@
"privateKey": "Clave privada",
"username": "Nombre",
"picture": "URL de imagen",
"lnurl": "LNURL",
"lnurl": "LNURL / Lightning address",
"about": "Descripción",
"publish": "Publicar"
},

View File

@ -16,7 +16,8 @@
"homePage": {
"search": "Поиск",
"send": "Отправить",
"noContacts": "У Вас пока нет контактов"
"noContacts": "У Вас пока нет контактов",
"addContacts": "Añadir contactos"
},
"logger": {
"randomKeyGenerator": {
@ -36,6 +37,15 @@
"notePage": {
"reply": "Ответить"
},
"loader": {
"searchingProfile": "Buscando tu perfil",
"profileFound": "Perfil encontrado",
"searchingContacts": "Buscando tus contactos:",
"help1": "¿Tienes problemas encontrando tus datos?",
"help2": "Intenta contectarte a otros Relays.",
"relays": "Relays",
"home": "Ir a Home"
},
"configPage": {
"title": "Настройки",
"logout": "Удалить ключи",
@ -44,7 +54,7 @@
"privateKey": "Приватный ключ",
"username": "Имя",
"picture": "URL изображения",
"lnurl": "LNURL",
"lnurl": "LNURL / Lightning address",
"about": "Описание",
"publish": "Опубликовать"
},