mirror of
https://github.com/KoalaSat/nostros.git
synced 2024-09-29 06:30:47 +00:00
Improve UX by providing visual feedback
This commit is contained in:
parent
d13e473431
commit
f42d28b456
38
frontend/Components/Button/index.tsx
Normal file
38
frontend/Components/Button/index.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import { View } from 'react-native'
|
||||
import { Button as UIKButton, ButtonProps as UIKButtonProps, Spinner } from '@ui-kitten/components'
|
||||
|
||||
interface ButtonProps extends UIKButtonProps {
|
||||
/** Indicates if the button is in "Loading" mode, which will disable it. */
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
const LoadingIndicator = (): ReactElement => (
|
||||
<View>
|
||||
<Spinner size='small' />
|
||||
</View>
|
||||
)
|
||||
|
||||
/**
|
||||
* Extension of the UI-Kitten button, with more features.
|
||||
* @param param0
|
||||
* @returns
|
||||
*/
|
||||
export const Button: React.FC<ButtonProps> = ({
|
||||
disabled,
|
||||
loading = false,
|
||||
children,
|
||||
accessoryLeft,
|
||||
...otherProps
|
||||
}) => {
|
||||
console.log('Is loading', loading)
|
||||
return (
|
||||
<UIKButton
|
||||
disabled={disabled ?? loading}
|
||||
accessoryLeft={loading ? LoadingIndicator : accessoryLeft}
|
||||
{...otherProps}
|
||||
>
|
||||
{children}
|
||||
</UIKButton>
|
||||
)
|
||||
}
|
@ -99,8 +99,15 @@
|
||||
"relayAdded": "Relay added",
|
||||
"relayRemoved": "Relay removed",
|
||||
"profilePublished": "Profile published",
|
||||
"profilePublishError": "There was an error while publishing the profile",
|
||||
"invoiceCopied": "Invoice copied",
|
||||
"invoiceError": "There was an error while trying to obtain the invoice"
|
||||
"invoiceError": "There was an error while trying to obtain the invoice",
|
||||
"sendNoteSuccess": "Note sent",
|
||||
"sendNoteError": "There was an error while sending the note",
|
||||
"sendGetNotesError": "There was an internal error retrieving notes",
|
||||
"contactAddError": "There was an error while adding the contact",
|
||||
"privateMessageEncryptError": "There was an error while encrypting the DM",
|
||||
"privateMessageSendError": "There was an error while sending the DM"
|
||||
},
|
||||
"note": {
|
||||
"contentWarning": "Sensitive content. Tap to show."
|
||||
|
@ -99,8 +99,15 @@
|
||||
"relayAdded": "Relé agregado",
|
||||
"relayRemoved": "Relé eliminado",
|
||||
"profilePublished": "Perfil publicado",
|
||||
"profilePublishError": "Se ha producido un error al tratar de publicar el perfil",
|
||||
"invoiceCopied": "Factura copiada",
|
||||
"invoiceError": "Se ha producido un error al tratar de obtener la factura"
|
||||
"invoiceError": "Se ha producido un error al tratar de obtener la factura",
|
||||
"sendNoteSuccess": "Nota enviada",
|
||||
"sendNoteError": "Se ha producido un error al tratar de enviar la nota",
|
||||
"sendGetNotesError": "Se ha producido un error interno al tratar de obtener las notas",
|
||||
"contactAddError": "Se ha producido un error al tratar de agregar el contacto",
|
||||
"privateMessageEncryptError": "Se ha producido un error al tratar de encriptar el mensaje",
|
||||
"privateMessageSendError": "Se ha producido un error al tratar de enviar el mensaje"
|
||||
},
|
||||
"note": {
|
||||
"contentWarning": "Contenido sensible. Toca para mostrar."
|
||||
|
@ -99,8 +99,15 @@
|
||||
"relayAdded": "Реле добавлено",
|
||||
"relayRemoved": "Реле удалено",
|
||||
"profilePublished": "Профиль опубликован",
|
||||
"profilePublishError": "При публикации профиля произошла ошибка",
|
||||
"invoiceCopied": "Инвойс копирован",
|
||||
"invoiceError": "Произошла ошибка при попытке получить инвойса."
|
||||
"invoiceError": "Произошла ошибка при попытке получить инвойса",
|
||||
"sendNoteSuccess": "Примечание отправлено",
|
||||
"sendNoteError": "При отправке заметки произошла ошибка",
|
||||
"sendGetNotesError": "При получении заметок произошла внутренняя ошибка",
|
||||
"contactAddError": "При добавлении контакта произошла ошибка",
|
||||
"privateMessageEncryptError": "При шифровании сообщения произошла ошибка",
|
||||
"privateMessageSendError": "При отправке сообщения произошла ошибка"
|
||||
},
|
||||
"note": {
|
||||
"contentWarning": "Деликатный контент. Нажмите, чтобы увидеть"
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Button, Divider, Input, Layout, TopNavigation, useTheme } from '@ui-kitten/components'
|
||||
import { Divider, Input, Layout, TopNavigation, useTheme } from '@ui-kitten/components'
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import { Clipboard, ScrollView, StyleSheet } from 'react-native'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
@ -11,16 +11,20 @@ import { getUser } from '../../Functions/DatabaseFunctions/Users'
|
||||
import { EventKind } from '../../lib/nostr/Events'
|
||||
import moment from 'moment'
|
||||
import { showMessage } from 'react-native-flash-message'
|
||||
import { Button } from '../../Components'
|
||||
|
||||
export const ConfigPage: React.FC = () => {
|
||||
const theme = useTheme()
|
||||
const { goToPage, goBack, database, init } = useContext(AppContext)
|
||||
const { setPrivateKey, setPublicKey, relayPool, publicKey, privateKey } =
|
||||
useContext(RelayPoolContext)
|
||||
// State
|
||||
const [name, setName] = useState<string>()
|
||||
const [picture, setPicture] = useState<string>()
|
||||
const [about, setAbout] = useState<string>()
|
||||
const [lnurl, setLnurl] = useState<string>()
|
||||
const [isPublishingProfile, setIsPublishingProfile] = useState<boolean>(false)
|
||||
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
useEffect(() => {
|
||||
@ -60,6 +64,7 @@ export const ConfigPage: React.FC = () => {
|
||||
|
||||
const onPushPublishProfile: () => void = () => {
|
||||
if (publicKey) {
|
||||
setIsPublishingProfile(true)
|
||||
relayPool
|
||||
?.sendEvent({
|
||||
content: JSON.stringify({
|
||||
@ -79,6 +84,15 @@ export const ConfigPage: React.FC = () => {
|
||||
duration: 4000,
|
||||
type: 'success',
|
||||
})
|
||||
setIsPublishingProfile(false) // restore sending status
|
||||
})
|
||||
.catch((err) => {
|
||||
showMessage({
|
||||
message: t('alerts.profilePublishError'),
|
||||
description: err.message,
|
||||
type: 'danger',
|
||||
})
|
||||
setIsPublishingProfile(false) // restore sending status
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -175,6 +189,7 @@ export const ConfigPage: React.FC = () => {
|
||||
<Button
|
||||
onPress={onPushPublishProfile}
|
||||
status='success'
|
||||
loading={isPublishingProfile}
|
||||
accessoryLeft={
|
||||
<Icon name='paper-plane' size={16} color={theme['text-basic-color']} solid />
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Input,
|
||||
Layout,
|
||||
@ -13,10 +12,11 @@ import React, { useContext, useEffect, useState } from 'react'
|
||||
import { ScrollView, StyleSheet, Text, TouchableOpacity } from 'react-native'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import Icon from 'react-native-vector-icons/FontAwesome5'
|
||||
import { showMessage } from 'react-native-flash-message'
|
||||
import { EventKind } from '../../lib/nostr/Events'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getUsers, updateUserContact, User } from '../../Functions/DatabaseFunctions/Users'
|
||||
import UserCard from '../../Components/UserCard'
|
||||
import { Button, UserCard } from '../../Components'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { populatePets } from '../../Functions/RelayFunctions/Users'
|
||||
|
||||
@ -24,10 +24,12 @@ export const ContactsPage: React.FC = () => {
|
||||
const { database, goBack } = useContext(AppContext)
|
||||
const { relayPool, publicKey, privateKey, lastEventId } = useContext(RelayPoolContext)
|
||||
const theme = useTheme()
|
||||
// State
|
||||
const [users, setUsers] = useState<User[]>()
|
||||
const [showAddContact, setShowAddContact] = useState<boolean>(false)
|
||||
const [contactInput, setContactInput] = useState<string>()
|
||||
const [selectedTab, setSelectedTab] = useState(0)
|
||||
const [isAddingContact, setIsAddingContact] = useState<boolean>(false)
|
||||
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
@ -82,11 +84,22 @@ export const ContactsPage: React.FC = () => {
|
||||
|
||||
const onPressAddContact: () => void = () => {
|
||||
if (contactInput && relayPool && database && publicKey) {
|
||||
updateUserContact(contactInput, database, true).then(() => {
|
||||
populatePets(relayPool, database, publicKey)
|
||||
setShowAddContact(false)
|
||||
loadUsers()
|
||||
})
|
||||
setIsAddingContact(true)
|
||||
updateUserContact(contactInput, database, true)
|
||||
.then(() => {
|
||||
populatePets(relayPool, database, publicKey)
|
||||
setShowAddContact(false)
|
||||
loadUsers()
|
||||
setIsAddingContact(false) // restore sending status
|
||||
})
|
||||
.catch((err) => {
|
||||
showMessage({
|
||||
message: t('alerts.contactAddError'),
|
||||
description: err.message,
|
||||
type: 'danger',
|
||||
})
|
||||
setIsAddingContact(false) // restore sending status
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,7 +183,7 @@ export const ContactsPage: React.FC = () => {
|
||||
/>
|
||||
</Layout>
|
||||
<Layout style={styles.button}>
|
||||
<Button onPress={onPressAddContact}>
|
||||
<Button onPress={onPressAddContact} loading={isAddingContact}>
|
||||
{<Text>{t('contactsPage.addContact.add')}</Text>}
|
||||
</Button>
|
||||
</Layout>
|
||||
|
@ -13,6 +13,8 @@ import { getUser, User } from '../../Functions/DatabaseFunctions/Users'
|
||||
import Avatar from '../../Components/Avatar'
|
||||
import { decrypt } from 'nostr-tools/nip04'
|
||||
import Icon from 'react-native-vector-icons/FontAwesome5'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { showMessage } from 'react-native-flash-message'
|
||||
import { username, usersToTags } from '../../Functions/RelayFunctions/Users'
|
||||
import moment from 'moment'
|
||||
import { encrypt } from '../../lib/nostr/Crypto'
|
||||
@ -23,14 +25,18 @@ export const ConversationPage: React.FC = () => {
|
||||
const scrollViewRef = useRef<ScrollView>()
|
||||
const { database, getActualPage, goBack, goToPage } = useContext(AppContext)
|
||||
const { relayPool, publicKey, lastEventId, privateKey } = useContext(RelayPoolContext)
|
||||
|
||||
const conversationId = getActualPage().split('#')[1]
|
||||
const otherPubKey = getActualPage().split('#')[2]
|
||||
// State
|
||||
const [directMessages, setDirectMessages] = useState<DirectMessage[]>([])
|
||||
const [sendingMessages, setSendingMessages] = useState<DirectMessage[]>([])
|
||||
const [otherUser, setOtherUser] = useState<User>({ id: otherPubKey })
|
||||
const [user, setUser] = useState<User>()
|
||||
const [input, setInput] = useState<string>('')
|
||||
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
useEffect(() => {
|
||||
loadDirectMessages()
|
||||
}, [lastEventId])
|
||||
@ -97,9 +103,24 @@ export const ConversationPage: React.FC = () => {
|
||||
setSendingMessages((prev) => [...prev, event as DirectMessage])
|
||||
setInput('')
|
||||
encrypt(privateKey, otherPubKey, input).then((content) => {
|
||||
relayPool?.sendEvent({
|
||||
...event,
|
||||
content,
|
||||
relayPool
|
||||
?.sendEvent({
|
||||
...event,
|
||||
content,
|
||||
})
|
||||
.catch((err) => {
|
||||
showMessage({
|
||||
message: t('alerts.privateMessageSendError'),
|
||||
description: err.message,
|
||||
type: 'danger',
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
showMessage({
|
||||
message: t('alerts.privateMessageEncryptError'),
|
||||
description: err.message,
|
||||
type: 'danger',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
Layout,
|
||||
List,
|
||||
@ -12,6 +11,7 @@ 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'
|
||||
import { showMessage } from 'react-native-flash-message'
|
||||
import { Event, EventKind } from '../../lib/nostr/Events'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
@ -20,18 +20,22 @@ import { getNotes } from '../../Functions/DatabaseFunctions/Notes'
|
||||
import { getETags } from '../../Functions/RelayFunctions/Events'
|
||||
import { getUsers, User } from '../../Functions/DatabaseFunctions/Users'
|
||||
import { formatPubKey } from '../../Functions/RelayFunctions/Users'
|
||||
import Avatar from '../../Components/Avatar'
|
||||
import { Avatar, Button } from '../../Components'
|
||||
|
||||
export const SendPage: React.FC = () => {
|
||||
const theme = useTheme()
|
||||
const { goBack, page, database } = useContext(AppContext)
|
||||
const { relayPool, publicKey } = useContext(RelayPoolContext)
|
||||
const { t } = useTranslation('common')
|
||||
const scrollViewRef = useRef<Input>()
|
||||
// state
|
||||
const [content, setContent] = useState<string>('')
|
||||
const [contentWarning, setContentWarning] = useState<boolean>(false)
|
||||
const [userSuggestions, setUserSuggestions] = useState<User[]>([])
|
||||
const [userMentions, setUserMentions] = useState<User[]>([])
|
||||
const [isSending, setIsSending] = useState<boolean>(false)
|
||||
|
||||
const scrollViewRef = useRef<Input>()
|
||||
|
||||
const breadcrump = page.split('%')
|
||||
const eventId = breadcrump[breadcrump.length - 1].split('#')[1]
|
||||
|
||||
@ -67,41 +71,68 @@ export const SendPage: React.FC = () => {
|
||||
|
||||
const onPressSend: () => void = () => {
|
||||
if (database && publicKey) {
|
||||
getNotes(database, { filters: { id: eventId } }).then((notes) => {
|
||||
let tags: string[][] = []
|
||||
const note = notes[0]
|
||||
setIsSending(true)
|
||||
getNotes(database, { filters: { id: eventId } })
|
||||
.then((notes) => {
|
||||
let tags: string[][] = []
|
||||
const note = notes[0]
|
||||
|
||||
let rawContent = content
|
||||
let rawContent = content
|
||||
|
||||
if (note) {
|
||||
tags = note.tags
|
||||
if (getETags(note).length === 0) {
|
||||
tags.push(['e', eventId, '', 'root'])
|
||||
} else {
|
||||
tags.push(['e', eventId, '', 'reply'])
|
||||
}
|
||||
}
|
||||
if (contentWarning) tags.push(['content-warning', ''])
|
||||
|
||||
if (userMentions.length > 0) {
|
||||
userMentions.forEach((user) => {
|
||||
const userText = mentionText(user)
|
||||
if (rawContent.includes(userText)) {
|
||||
rawContent = rawContent.replace(userText, `#[${tags.length}]`)
|
||||
tags.push(['p', user.id])
|
||||
if (note) {
|
||||
tags = note.tags
|
||||
if (getETags(note).length === 0) {
|
||||
tags.push(['e', eventId, '', 'root'])
|
||||
} else {
|
||||
tags.push(['e', eventId, '', 'reply'])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
if (contentWarning) tags.push(['content-warning', ''])
|
||||
|
||||
const event: Event = {
|
||||
content: rawContent,
|
||||
created_at: moment().unix(),
|
||||
kind: EventKind.textNote,
|
||||
pubkey: publicKey,
|
||||
tags,
|
||||
}
|
||||
relayPool?.sendEvent(event).then(() => goBack())
|
||||
})
|
||||
if (userMentions.length > 0) {
|
||||
userMentions.forEach((user) => {
|
||||
const userText = mentionText(user)
|
||||
if (rawContent.includes(userText)) {
|
||||
rawContent = rawContent.replace(userText, `#[${tags.length}]`)
|
||||
tags.push(['p', user.id])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const event: Event = {
|
||||
content: rawContent,
|
||||
created_at: moment().unix(),
|
||||
kind: EventKind.textNote,
|
||||
pubkey: publicKey,
|
||||
tags,
|
||||
}
|
||||
relayPool
|
||||
?.sendEvent(event)
|
||||
.then(() => {
|
||||
showMessage({
|
||||
message: t('alerts.sendNoteSuccess'),
|
||||
type: 'success',
|
||||
})
|
||||
setIsSending(false) // restore sending status
|
||||
goBack()
|
||||
})
|
||||
.catch((err) => {
|
||||
showMessage({
|
||||
message: t('alerts.sendNoteError'),
|
||||
description: err.message,
|
||||
type: 'danger',
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
// error with getNotes
|
||||
showMessage({
|
||||
message: t('alerts.sendGetNotesError'),
|
||||
description: err.message,
|
||||
type: 'danger',
|
||||
})
|
||||
setIsSending(false) // restore sending status
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,7 +199,9 @@ export const SendPage: React.FC = () => {
|
||||
/>
|
||||
</Layout>
|
||||
<Layout style={styles.button}>
|
||||
<Button onPress={onPressSend}>{t('sendPage.send')}</Button>
|
||||
<Button onPress={onPressSend} loading={isSending}>
|
||||
{t('sendPage.send')}
|
||||
</Button>
|
||||
</Layout>
|
||||
<Layout style={styles.button} level='2'>
|
||||
<Toggle checked={contentWarning} onChange={setContentWarning}>
|
||||
|
Loading…
Reference in New Issue
Block a user