Skip to content

Commit

Permalink
update nostr contacts screen. Fixes #132
Browse files Browse the repository at this point in the history
  • Loading branch information
KKA11010 committed Aug 19, 2023
1 parent c3ef45b commit a1f3421
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 47 deletions.
2 changes: 2 additions & 0 deletions assets/translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@
"version": "Version",
"willDoLater": "Mache ich später",
"yes": "Ja",
"invalidPubKey": "Invalider öffentlicher Schlüssel!",
"whatsNostr": "Was ist NOSTR?",
"walletLocked": "Wallet gesperrt",
"explainer1": "eNuts ist eine treuhänderische Lightning-Wallet, die private und sofortige Transaktionen mit dem Cashu-Protokoll ermöglicht. Ihre Gelder werden von Mints verwaltet, mit denen Sie interagieren, und Ecash wird lokal auf Ihrem Gerät gespeichert.",
"explainer2": "Cashu ist ein neues Ecash-Protokoll für treuhänderische Bitcoin-Apps, bei denen Mints nur Lightning-Knoten sind, die Bitcoin-Transaktionen durchführen und Ihnen Ecash zur Verfügung stellen. Seien Sie versichert, die Mints bleiben unwissend über Ihre Ecash-Zahlungen.",
Expand Down
2 changes: 2 additions & 0 deletions assets/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@
"version": "Version",
"willDoLater": "Will do later",
"yes": "Yes",
"invalidPubKey": "Invalid public key!",
"whatsNostr": "What is NOSTR?",
"walletLocked": "Wallet locked",
"explainer1": "eNuts is a custodial Lightning wallet, allowing private and instant transactions using the Cashu protocol. Your funds are held by mints you interact with, and Ecash is stored locally on your device.",
"explainer2": "Cashu is a new Ecash protocol for custodial Bitcoin apps, where mints are Lightning nodes performing Bitcoin transactions and offer you Ecash. Rest assured, the mint remains unaware of your Ecash payments.",
Expand Down
2 changes: 2 additions & 0 deletions assets/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@
"version": "Version",
"willDoLater": "Je ferai plus tard",
"yes": "Oui",
"invalidPubKey": "Clé publique invalide!",
"whatsNostr": "C'est quoi NOSTR?",
"walletLocked": "Wallet verrouillé",
"explainer1": "eNuts est une wallet Lightning sous garde, permettant des transactions privées et instantanées en utilisant le protocole Cashu. Vos fonds sont détenus par des mints avec lesquels vous interagissez, et l'Ecash est stocké localement sur votre appareil.",
"explainer2": "Cashu est un nouveau protocole Ecash pour les applications Bitcoin sous garde, où les mints sont des nœuds Lightning facilitant les transactions Bitcoin pour vous offrir de l'Ecash. Soyez rassuré, les mints reste ignorant de vos paiements Ecash.",
Expand Down
23 changes: 17 additions & 6 deletions src/components/Empty.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ import type { RootStackParamList } from '@model/nav'
import type { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { useThemeContext } from '@src/context/Theme'
import { useTranslation } from 'react-i18next'
import { Image, StyleSheet } from 'react-native'
import { Image, StyleSheet, TouchableOpacity } from 'react-native'

import { TxtButton } from './Button'
import Txt from './Txt'

interface IEmptyProps {
txt: string
hasOk?: boolean
pressable?: boolean
onPress?: () => void
nav?: NativeStackNavigationProp<RootStackParamList, 'nostrReceive', 'MyStack'>
}

export default function Empty({ txt, hasOk, nav }: IEmptyProps) {
export default function Empty({ txt, hasOk, pressable, onPress, nav }: IEmptyProps) {
const { t } = useTranslation()
const { color } = useThemeContext()
return (
Expand All @@ -23,10 +25,19 @@ export default function Empty({ txt, hasOk, nav }: IEmptyProps) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
source={require('@assets/mixed_forest.png')}
/>
<Txt
txt={txt}
styles={[styles.emptyTxt, { color: color.TEXT_SECONDARY, marginBottom: hasOk ? 10 : 0 }]}
/>
{pressable && onPress ?
<TouchableOpacity>
<TxtButton
txt={txt}
onPress={onPress}
/>
</TouchableOpacity>
:
<Txt
txt={txt}
styles={[styles.emptyTxt, { color: color.TEXT_SECONDARY, marginBottom: hasOk ? 10 : 0 }]}
/>
}
{hasOk &&
<TxtButton
txt={t('backToDashboard')}
Expand Down
117 changes: 76 additions & 41 deletions src/screens/Addressbook/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import Button, { TxtButton } from '@comps/Button'
import Empty from '@comps/Empty'
import useLoading from '@comps/hooks/Loading'
import InputAndLabel from '@comps/InputAndLabel'
import Loading from '@comps/Loading'
import MyModal from '@comps/modal'
import Separator from '@comps/Separator'
import { isIOS } from '@consts'
Expand All @@ -22,7 +25,7 @@ import { secureStore, store } from '@store'
import { SECRET, STORE_KEYS } from '@store/consts'
import { getCustomMintNames } from '@store/mintStore'
import { globals } from '@styles'
import { getStrFromClipboard, isStr } from '@util'
import { getStrFromClipboard, isStr, openUrl } from '@util'
import { type Event as NostrEvent, generatePrivateKey, getPublicKey, nip19 } from 'nostr-tools'
import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
Expand Down Expand Up @@ -51,6 +54,7 @@ export default function AddressbookPage({ navigation, route }: TAddressBookPageP
contacts,
setContacts
} = useNostrContext()
const { loading, startLoading, stopLoading } = useLoading()
const [, setAlreadySeen] = useState<string[]>([])
const [newNpubModal, setNewNpubModal] = useState(false)

Expand Down Expand Up @@ -184,13 +188,20 @@ export default function AddressbookPage({ navigation, route }: TAddressBookPageP
}
// paste from clipboard
const clipboard = await getStrFromClipboard()
if (!clipboard || clipboard === 'null') { return }
if (!clipboard) {
return
}
// check if is npub
if (clipboard.startsWith('npub')) {
setPubKey({ encoded: clipboard, hex: nip19.decode(clipboard).data as string || '' })
return
}
setPubKey({ encoded: nip19.npubEncode(clipboard), hex: clipboard })
try {
const encoded = nip19.npubEncode(clipboard)
setPubKey({ encoded, hex: clipboard })
} catch (e) {
openPromptAutoClose({ msg: t('invalidPubKey') })
}
}

// save npub pasted by user
Expand Down Expand Up @@ -286,6 +297,7 @@ export default function AddressbookPage({ navigation, route }: TAddressBookPageP
// check if user has nostr data saved previously
useEffect(() => {
void (async () => {
startLoading()
const data = await Promise.all([
store.get(STORE_KEYS.npub),
store.get(STORE_KEYS.npubHex),
Expand All @@ -294,6 +306,8 @@ export default function AddressbookPage({ navigation, route }: TAddressBookPageP
setPubKey({ encoded: data[0] || '', hex: data[1] || '' })
setUserRelays(data[2] || [])
initUserData({ hex: data[1] || '', userRelays: data[2] || [] })
if (!data[0]) { setNewNpubModal(true) }
stopLoading()
})()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
Expand All @@ -309,52 +323,66 @@ export default function AddressbookPage({ navigation, route }: TAddressBookPageP
<View style={styles.bookHeader}>
<ContactsCount />
</View>
{/* user own profile */}
<UserProfile handlePress={handleContactPress} />
{/* user contacts */}
{contacts.length > 0 &&
<View style={[
globals(color).wrapContainer,
styles.contactsWrap,
{ marginBottom: route.params?.isMelt || route.params?.isSendEcash ? marginBottomPayment : marginBottom }
]}>
<FlashList
data={contacts}
estimatedItemSize={300}
viewabilityConfig={{
minimumViewTime: 250,
itemVisiblePercentThreshold: 10,
}}
onViewableItemsChanged={onViewableItemsChanged}
keyExtractor={item => item[0]}
renderItem={({ item, index }) => (
<ContactPreview
contact={item}
handleContactPress={() => handleContactPress({ contact: item[1], npub: nip19.npubEncode(item[0]) })}
handleSend={() => {
void handleSend({
npub: item[0],
name: getNostrUsername(item[1])
})
{loading ?
<View style={styles.loadingWrap}>
<Loading />
</View>
:
<>
{/* user own profile */}
{nutPub && <UserProfile handlePress={handleContactPress} />}
{/* user contacts */}
{contacts.length > 0 ?
<View style={[
globals(color).wrapContainer,
styles.contactsWrap,
{ marginBottom: route.params?.isMelt || route.params?.isSendEcash ? marginBottomPayment : marginBottom }
]}>
<FlashList
data={contacts}
estimatedItemSize={300}
viewabilityConfig={{
minimumViewTime: 250,
itemVisiblePercentThreshold: 10,
}}
isFirst={index === 0}
isLast={index === contacts.length - 1}
isPayment={route.params?.isMelt || route.params?.isSendEcash}
onViewableItemsChanged={onViewableItemsChanged}
keyExtractor={item => item[0]}
renderItem={({ item, index }) => (
<ContactPreview
contact={item}
handleContactPress={() => handleContactPress({ contact: item[1], npub: nip19.npubEncode(item[0]) })}
handleSend={() => {
void handleSend({
npub: item[0],
name: getNostrUsername(item[1])
})
}}
isFirst={index === 0}
isLast={index === contacts.length - 1}
isPayment={route.params?.isMelt || route.params?.isSendEcash}
/>
)}
ItemSeparatorComponent={() => <Separator style={[styles.contactSeparator]} />}
/>
)}
ItemSeparatorComponent={() => <Separator style={[styles.contactSeparator]} />}
/>
</View>
</View>
:
<Empty
txt={newNpubModal ? '' : t('addOwnLnurl', { ns: NS.addrBook })}
pressable={!newNpubModal}
onPress={() => setNewNpubModal(true)}
/>
}
</>
}
{/* Add user npub modal */}
<MyModal
type='bottom'
animation='slide'
visible={!nutPub || newNpubModal}
visible={newNpubModal}
close={() => setNewNpubModal(false)}
>
<Text style={globals(color).modalHeader}>
{t('yourProfile', { ns: NS.addrBook })}
{t('addOwnLnurl', { ns: NS.addrBook })}
</Text>
<InputAndLabel
placeholder='NPUB/HEX'
Expand All @@ -365,8 +393,9 @@ export default function AddressbookPage({ navigation, route }: TAddressBookPageP
isEmptyInput={pubKey.encoded.length < 1}
/>
<Button
txt={t('save')}
onPress={() => void handleNewNpub()}
outlined
txt={t('whatsNostr')}
onPress={() => void openUrl('https://nostr-resources.com/')}
/>
<TxtButton
txt={t('cancel')}
Expand Down Expand Up @@ -398,6 +427,12 @@ const styles = StyleSheet.create({
container: {
paddingTop: 0
},
loadingWrap: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
marginBottom: 100,
},
bookHeader: {
paddingHorizontal: 20,
marginBottom: 20,
Expand Down

0 comments on commit a1f3421

Please sign in to comment.