Improve UX by adding more visual feedback (#99)

This commit is contained in:
KoalaSat 2023-01-08 20:17:26 +00:00 committed by GitHub
commit 7bdd51a046
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 203 additions and 53 deletions

View 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>
)
}

View File

@ -5,11 +5,11 @@ import { StyleSheet } from 'react-native'
import { AppContext } from '../../Contexts/AppContext'
import Avatar from '../Avatar'
interface NoteCardProps {
interface UserCardProps {
user: User
}
export const NoteCard: React.FC<NoteCardProps> = ({ user }) => {
export const UserCard: React.FC<UserCardProps> = ({ user }) => {
const { goToPage } = useContext(AppContext)
const styles = StyleSheet.create({
@ -52,4 +52,4 @@ export const NoteCard: React.FC<NoteCardProps> = ({ user }) => {
)
}
export default NoteCard
export default UserCard

View File

@ -0,0 +1,7 @@
/**
* Main index for all components, for easy import into files
*/
export { Avatar } from './Avatar'
export { Button } from './Button'
export { UserCard } from './UserCard'

View File

@ -100,8 +100,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."

View File

@ -100,8 +100,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."

View File

@ -100,8 +100,15 @@
"relayAdded": "Реле добавлено",
"relayRemoved": "Реле удалено",
"profilePublished": "Профиль опубликован",
"profilePublishError": "При публикации профиля произошла ошибка",
"invoiceCopied": "Инвойс копирован",
"invoiceError": "Произошла ошибка при попытке получить инвойса."
"invoiceError": "Произошла ошибка при попытке получить инвойса",
"sendNoteSuccess": "Примечание отправлено",
"sendNoteError": "При отправке заметки произошла ошибка",
"sendGetNotesError": "При получении заметок произошла внутренняя ошибка",
"contactAddError": "При добавлении контакта произошла ошибка",
"privateMessageEncryptError": "При шифровании сообщения произошла ошибка",
"privateMessageSendError": "При отправке сообщения произошла ошибка"
},
"note": {
"contentWarning": "Деликатный контент. Нажмите, чтобы увидеть"

View File

@ -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,19 @@ 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 [nip05, setNip05] = useState<string>()
const { t } = useTranslation('common')
@ -62,6 +65,7 @@ export const ConfigPage: React.FC = () => {
const onPushPublishProfile: () => void = () => {
if (publicKey) {
setIsPublishingProfile(true)
relayPool
?.sendEvent({
content: JSON.stringify({
@ -82,6 +86,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
})
}
}
@ -186,6 +199,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 />
}

View File

@ -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>

View File

@ -12,6 +12,8 @@ import {
import { getUser, User } from '../../Functions/DatabaseFunctions/Users'
import Avatar from '../../Components/Avatar'
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 TextContent from '../../Components/TextContent'
@ -22,14 +24,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])
@ -95,10 +101,28 @@ export const ConversationPage: React.FC = () => {
}
setSendingMessages((prev) => [...prev, event as DirectMessage])
setInput('')
const encryptedcontent = encrypt(privateKey, otherPubKey, input)
relayPool?.sendEvent({
...event,
content: encryptedcontent,
encrypt(privateKey, otherPubKey, input).then((content) => {
relayPool
?.sendEvent({
...event,
content: encryptedcontent,
})
.catch((err) => {
showMessage({
message: t('alerts.privateMessageSendError'),
description: err.message,
type: 'danger',
})
})
})
.catch((err) => {
showMessage({
message: t('alerts.privateMessageEncryptError'),
description: err.message,
type: 'danger',
})
})
}
}

View File

@ -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}>