Share contact and Upload image preview (#221)

This commit is contained in:
KoalaSat 2023-01-31 09:46:55 +00:00 committed by GitHub
commit 25de204b0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 263 additions and 53 deletions

View File

@ -2,6 +2,8 @@
package="com.nostros"> package="com.nostros">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<!-- required for react-native-share base64 sharing -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application <application
android:name=".MainApplication" android:name=".MainApplication"

View File

@ -156,8 +156,8 @@ export const LnPayment: React.FC<TextContentProps> = ({ open, setOpen, event, us
> >
<Card style={styles.qrContainer}> <Card style={styles.qrContainer}>
<Card.Content> <Card.Content>
<View> <View style={styles.qr}>
<QRCode value={invoice} size={350} /> <QRCode value={invoice} size={300} quietZone={8}/>
</View> </View>
<View style={styles.qrText}> <View style={styles.qrText}>
<Text>{monto} </Text> <Text>{monto} </Text>
@ -222,6 +222,11 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-around', justifyContent: 'space-around',
}, },
qr: {
justifyContent: 'center',
alignItems: 'center',
padding: 16
},
}) })
export default LnPayment export default LnPayment

View File

@ -2,7 +2,7 @@ import { t } from 'i18next'
import * as React from 'react' import * as React from 'react'
import { StyleSheet, View } from 'react-native' import { StyleSheet, View } from 'react-native'
import Clipboard from '@react-native-clipboard/clipboard' import Clipboard from '@react-native-clipboard/clipboard'
import { Card, IconButton, Snackbar, Text, useTheme } from 'react-native-paper' import { Card, IconButton, Snackbar, Text, TouchableRipple, useTheme } from 'react-native-paper'
import { AppContext } from '../../Contexts/AppContext' import { AppContext } from '../../Contexts/AppContext'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext' import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
import { UserContext } from '../../Contexts/UserContext' import { UserContext } from '../../Contexts/UserContext'
@ -12,6 +12,7 @@ import {
updateUserContact, updateUserContact,
User, User,
} from '../../Functions/DatabaseFunctions/Users' } from '../../Functions/DatabaseFunctions/Users'
import Share from 'react-native-share'
import { populatePets, usernamePubKey } from '../../Functions/RelayFunctions/Users' import { populatePets, usernamePubKey } from '../../Functions/RelayFunctions/Users'
import LnPayment from '../LnPayment' import LnPayment from '../LnPayment'
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons' import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
@ -19,6 +20,7 @@ import { navigate, push } from '../../lib/Navigation'
import RBSheet from 'react-native-raw-bottom-sheet' import RBSheet from 'react-native-raw-bottom-sheet'
import { getNpub } from '../../lib/nostr/Nip19' import { getNpub } from '../../lib/nostr/Nip19'
import ProfileData from '../ProfileData' import ProfileData from '../ProfileData'
import QRCode from 'react-native-qrcode-svg'
interface ProfileCardProps { interface ProfileCardProps {
userPubKey: string userPubKey: string
@ -32,6 +34,7 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({
showImages = true, showImages = true,
}) => { }) => {
const theme = useTheme() const theme = useTheme()
const bottomSheetShareRef = React.useRef<RBSheet>(null)
const { database } = React.useContext(AppContext) const { database } = React.useContext(AppContext)
const { publicKey } = React.useContext(UserContext) const { publicKey } = React.useContext(UserContext)
const { relayPool } = React.useContext(RelayPoolContext) const { relayPool } = React.useContext(RelayPoolContext)
@ -40,6 +43,7 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({
const [openLn, setOpenLn] = React.useState<boolean>(false) const [openLn, setOpenLn] = React.useState<boolean>(false)
const [isContact, setIsContact] = React.useState<boolean>() const [isContact, setIsContact] = React.useState<boolean>()
const [showNotification, setShowNotification] = React.useState<undefined | string>() const [showNotification, setShowNotification] = React.useState<undefined | string>()
const [qrCode, setQrCode] = React.useState<any>()
const nPub = React.useMemo(() => getNpub(userPubKey), [userPubKey]) const nPub = React.useMemo(() => getNpub(userPubKey), [userPubKey])
const username = React.useMemo(() => usernamePubKey(user?.name ?? '', nPub), [nPub, user]) const username = React.useMemo(() => usernamePubKey(user?.name ?? '', nPub), [nPub, user])
@ -96,6 +100,21 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({
push('Profile', { pubKey: userPubKey, title: username }) push('Profile', { pubKey: userPubKey, title: username })
} }
const bottomSheetStyles = React.useMemo(() => {
return {
container: {
backgroundColor: theme.colors.background,
paddingTop: 16,
paddingRight: 16,
paddingBottom: 32,
paddingLeft: 16,
borderTopRightRadius: 28,
borderTopLeftRadius: 28,
height: 'auto',
},
}
}, [])
return ( return (
<View> <View>
<Card onPress={goToProfile}> <Card onPress={goToProfile}>
@ -144,14 +163,6 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({
<Text>{isContact ? t('profileCard.unfollow') : t('profileCard.follow')}</Text> <Text>{isContact ? t('profileCard.unfollow') : t('profileCard.follow')}</Text>
</View> </View>
)} )}
<View style={styles.actionButton}>
<IconButton
icon={blocked ? 'account-cancel' : 'account-cancel-outline'}
size={28}
onPress={onChangeBlockUser}
/>
<Text>{t(blocked ? 'profileCard.unblock' : 'profileCard.block')}</Text>
</View>
<View style={styles.actionButton}> <View style={styles.actionButton}>
<IconButton <IconButton
icon='message-plus-outline' icon='message-plus-outline'
@ -165,14 +176,13 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({
</View> </View>
<View style={styles.actionButton}> <View style={styles.actionButton}>
<IconButton <IconButton
icon='content-copy' icon='share-variant-outline'
size={28} size={28}
onPress={() => { onPress={() => {
setShowNotification('npubCopied') bottomSheetShareRef.current?.open()
Clipboard.setString(nPub ?? '')
}} }}
/> />
<Text>{t('profileCard.copyNPub')}</Text> <Text>{t('profileCard.share')}</Text>
</View> </View>
{user?.lnurl && ( {user?.lnurl && (
<View style={styles.actionButton}> <View style={styles.actionButton}>
@ -187,6 +197,14 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({
</> </>
</View> </View>
)} )}
<View style={styles.actionButton}>
<IconButton
icon={blocked ? 'account-cancel' : 'account-cancel-outline'}
size={28}
onPress={onChangeBlockUser}
/>
<Text>{t(blocked ? 'profileCard.unblock' : 'profileCard.block')}</Text>
</View>
</View> </View>
{showNotification && ( {showNotification && (
<Snackbar <Snackbar
@ -200,6 +218,60 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({
</Snackbar> </Snackbar>
)} )}
<LnPayment setOpen={setOpenLn} open={openLn} user={user} /> <LnPayment setOpen={setOpenLn} open={openLn} user={user} />
<RBSheet ref={bottomSheetShareRef} closeOnDragDown={true} customStyles={bottomSheetStyles}>
<View style={styles.mainLayout}>
<View style={styles.qr}>
<TouchableRipple
onPress={() => {
if (qrCode) {
qrCode.toDataURL((base64: string) => {
Share.open({
url: `data:image/png;base64,${base64}`,
filename: user?.id ?? 'nostrosshare'
})
})
}
}}
>
<QRCode
quietZone={8}
value={`nostr:${nPub}`}
size={350}
logoBorderRadius={50}
logoSize={100}
logo={{ uri: user?.picture }}
getRef={setQrCode}
/>
</TouchableRipple>
</View>
<View style={styles.shareActionButton}>
<IconButton
icon='key-outline'
size={28}
onPress={() => {
setShowNotification('npubCopied')
Clipboard.setString(nPub ?? '')
bottomSheetShareRef.current?.close()
}}
/>
<Text>{t('profileCard.copyNPub')}</Text>
</View>
{user?.nip05 && (
<View style={styles.shareActionButton}>
<IconButton
icon='check-decagram-outline'
size={28}
onPress={() => {
setShowNotification('npubCopied')
Clipboard.setString(user?.nip05 ?? '')
bottomSheetShareRef.current?.close()
}}
/>
<Text>{t('profileCard.copyNip05')}</Text>
</View>
)}
</View>
</RBSheet>
</View> </View>
) )
} }
@ -247,7 +319,18 @@ const styles = StyleSheet.create({
actionButton: { actionButton: {
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
flexBasis: '33.333333%', flexBasis: '25%',
marginBottom: 4,
},
qr: {
justifyContent: 'center',
alignItems: 'center',
padding: 16,
},
shareActionButton: {
justifyContent: 'center',
alignItems: 'center',
flexBasis: '50%',
marginBottom: 4, marginBottom: 4,
}, },
list: { list: {

View File

@ -6,6 +6,7 @@ import { Linking, StyleSheet } from 'react-native'
import { Text } from 'react-native-paper' import { Text } from 'react-native-paper'
import { Config } from '../Pages/ConfigPage' import { Config } from '../Pages/ConfigPage'
import { imageHostingServices } from '../Constants/Services' import { imageHostingServices } from '../Constants/Services'
import { randomInt } from '../Functions/NativeFunctions'
export interface AppContextProps { export interface AppContextProps {
init: () => void init: () => void
@ -22,6 +23,7 @@ export interface AppContextProps {
setImageHostingService: (imageHostingService: string) => void setImageHostingService: (imageHostingService: string) => void
setSatoshi: (showPublicImages: 'kebab' | 'sats') => void setSatoshi: (showPublicImages: 'kebab' | 'sats') => void
getSatoshiSymbol: (fontSize?: number) => JSX.Element getSatoshiSymbol: (fontSize?: number) => JSX.Element
getImageHostingService: () => string
} }
export interface AppContextProviderProps { export interface AppContextProviderProps {
@ -42,6 +44,7 @@ export const initialAppContext: AppContextProps = {
setSatoshi: () => {}, setSatoshi: () => {},
imageHostingService: Object.keys(imageHostingServices)[0], imageHostingService: Object.keys(imageHostingServices)[0],
setImageHostingService: () => {}, setImageHostingService: () => {},
getImageHostingService: () => "",
getSatoshiSymbol: () => <></>, getSatoshiSymbol: () => <></>,
} }
@ -101,6 +104,14 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E
) )
} }
const getImageHostingService: () => string = () => {
if (imageHostingService !== 'random') return imageHostingService
const randomIndex = randomInt(1, Object.keys(imageHostingServices).length)
return Object.keys(imageHostingServices)[randomIndex - 1]
}
useEffect(init, []) useEffect(init, [])
return ( return (
@ -108,6 +119,7 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E
value={{ value={{
imageHostingService, imageHostingService,
setImageHostingService, setImageHostingService,
getImageHostingService,
init, init,
loadingDb, loadingDb,
database, database,

View File

@ -61,3 +61,5 @@ export const validNip21: (string: string | undefined) => boolean = (string) => {
return false return false
} }
} }
export const randomInt: (min: number, max: number) => number = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min

View File

@ -47,8 +47,11 @@
"isNotContact": "Not following", "isNotContact": "Not following",
"contentWarning": "Sensitive content", "contentWarning": "Sensitive content",
"send": "Send", "send": "Send",
"imageUploaded": "Your file has been uploaded.\nConsider donating to the service: {{uri}}", "imageUploaded": "Your file has been uploaded.\nConsider donating to the service:\n{{uri}}",
"imageUploadErro": "There was an error while trying to upload your file" "imageUploadErro": "There was an error while trying to upload your file",
"uploadImage": "Upload image now",
"cancel": "Cancel",
"poweredBy": "Powered by {{uri}}"
}, },
"menuItems": { "menuItems": {
"relays": "Relays", "relays": "Relays",
@ -64,7 +67,8 @@
"showPublicImages": "Show images on public feed", "showPublicImages": "Show images on public feed",
"showSensitive": "Show sensitive notes", "showSensitive": "Show sensitive notes",
"satoshi": "Satoshi symbol", "satoshi": "Satoshi symbol",
"imageHostingService": "Image hosting service" "imageHostingService": "Image hosting service",
"random": "Random"
}, },
"noteCard": { "noteCard": {
"answering": "Answer to {{pubkey}}", "answering": "Answer to {{pubkey}}",
@ -233,6 +237,8 @@
"unfollow": "Following", "unfollow": "Following",
"block": "Block", "block": "Block",
"unblock": "Unblock", "unblock": "Unblock",
"share": "Share",
"copyNip05": "Copy NIP-05",
"copyNPub": "Copy key" "copyNPub": "Copy key"
}, },
"conversationsFeed": { "conversationsFeed": {

View File

@ -46,7 +46,12 @@
"isContact": "Siguiendo", "isContact": "Siguiendo",
"isNotContact": "Sin seguir", "isNotContact": "Sin seguir",
"contentWarning": "Contenido sensible", "contentWarning": "Contenido sensible",
"send": "Enviar" "send": "Enviar",
"imageUploaded": "Tu archivo se ha subido.\nConsidera donar al servicio:\n{{uri}}",
"imageUploadErro": "Se ha producido un error al subir la imagen.",
"uploadImage": "Subir imagen ahora",
"cancel": "Cancelar",
"poweredBy": "Servido por {{uri}}"
}, },
"menuItems": { "menuItems": {
"relays": "Relays", "relays": "Relays",
@ -62,7 +67,8 @@
"showPublicImages": "Mostrar imágenes en feed global", "showPublicImages": "Mostrar imágenes en feed global",
"showSensitive": "Mostrar notas sensibles", "showSensitive": "Mostrar notas sensibles",
"satoshi": "Símbolo de satoshi", "satoshi": "Símbolo de satoshi",
"imageHostingService": "Servicio de subida de imágenes" "imageHostingService": "Servicio de subida de imágenes",
"random": "Aleatorio"
}, },
"noteCard": { "noteCard": {
"answering": "Responder a {{pubkey}}", "answering": "Responder a {{pubkey}}",
@ -217,8 +223,10 @@
"message": "Mensaje", "message": "Mensaje",
"follow": "Seguir", "follow": "Seguir",
"block": "Bloquear", "block": "Bloquear",
"share": "Share",
"unblock": "Desbloquear", "unblock": "Desbloquear",
"unfollow": "Siguiendo", "unfollow": "Siguiendo",
"copyNip05": "Copiar NIP-05",
"copyNPub": "Copiar clave" "copyNPub": "Copiar clave"
}, },
"conversationsFeed": { "conversationsFeed": {

View File

@ -46,7 +46,12 @@
"isContact": "Following", "isContact": "Following",
"isNotContact": "Not following", "isNotContact": "Not following",
"contentWarning": "Делекатный контент", "contentWarning": "Делекатный контент",
"send": "Send" "send": "Send",
"imageUploaded": "Tu archivo se ha subido.\nConsidera donar al servicio:\n{{uri}}",
"imageUploadErro": "Se ha producido un error al subir la imagen.",
"uploadImage": "Subir imagen ahora",
"cancel": "Отменить",
"poweredBy": "Powered by {{uri}}"
}, },
"menuItems": { "menuItems": {
"relays": "Реле", "relays": "Реле",
@ -62,7 +67,8 @@
"configPage": { "configPage": {
"showPublicImages": "Show images on public feed", "showPublicImages": "Show images on public feed",
"showSensitive": "Show sensitive notes", "showSensitive": "Show sensitive notes",
"satoshi": "Satoshi symbol" "satoshi": "Satoshi symbol",
"random": "Random"
}, },
"noteCard": { "noteCard": {
"answering": "Ответить {{pubkey}}", "answering": "Ответить {{pubkey}}",
@ -218,6 +224,8 @@
"block": "Block", "block": "Block",
"unblock": "Desbloquear", "unblock": "Desbloquear",
"unfollow": "Following", "unfollow": "Following",
"share": "Share",
"copyNip05": "Copy NIP-05",
"copyNPub": "Copy key" "copyNPub": "Copy key"
}, },
"conversationsFeed": { "conversationsFeed": {

View File

@ -67,10 +67,10 @@ export const ConfigPage: React.FC = () => {
}, []) }, [])
const imageHostingOptions = React.useMemo(() => { const imageHostingOptions = React.useMemo(() => {
return Object.keys(imageHostingServices).map((service, index) => { return ['random', ...Object.keys(imageHostingServices)].map((service, index) => {
return { return {
key: index, key: index,
title: <Text>{imageHostingServices[service].uri}</Text>, title: <Text>{imageHostingServices[service]?.uri ?? t(`configPage.${service}`)}</Text>,
onPress: () => { onPress: () => {
setImageHostingService(service) setImageHostingService(service)
SInfo.getItem('config', {}).then((result) => { SInfo.getItem('config', {}).then((result) => {
@ -141,7 +141,12 @@ export const ConfigPage: React.FC = () => {
<List.Item <List.Item
title={t('configPage.imageHostingService')} title={t('configPage.imageHostingService')}
onPress={() => bottomSheetImageHostingRef.current?.open()} onPress={() => bottomSheetImageHostingRef.current?.open()}
right={() => <Text>{imageHostingServices[imageHostingService].uri}</Text>} right={() => (
<Text>
{imageHostingServices[imageHostingService]?.uri ??
t(`configPage.${imageHostingService}`)}
</Text>
)}
/> />
<RBSheet ref={bottomSheetSatoshiRef} closeOnDragDown={true} customStyles={bottomSheetStyles}> <RBSheet ref={bottomSheetSatoshiRef} closeOnDragDown={true} customStyles={bottomSheetStyles}>
<FlatList <FlatList

View File

@ -167,15 +167,19 @@ export const RelaysPage: React.FC = () => {
)} )}
<RBSheet ref={bottomSheetAddRef} closeOnDragDown={true} customStyles={rbSheetCustomStyles}> <RBSheet ref={bottomSheetAddRef} closeOnDragDown={true} customStyles={rbSheetCustomStyles}>
<View style={styles.addRelay}> <View style={styles.addRelay}>
<TextInput <View style={styles.bottomDrawerButton}>
mode='outlined' <TextInput
label={t('relaysPage.labelAdd') ?? ''} mode='outlined'
onChangeText={setAddRelayInput} label={t('relaysPage.labelAdd') ?? ''}
value={addRelayInput} onChangeText={setAddRelayInput}
/> value={addRelayInput}
<Button mode='contained' onPress={onPressAddRelay}> />
{t('relaysPage.add')} </View>
</Button> <View style={styles.bottomDrawerButton}>
<Button mode='contained' onPress={onPressAddRelay}>
{t('relaysPage.add')}
</Button>
</View>
<Button <Button
mode='outlined' mode='outlined'
onPress={() => { onPress={() => {
@ -224,6 +228,9 @@ const styles = StyleSheet.create({
title: { title: {
paddingLeft: 16, paddingLeft: 16,
}, },
bottomDrawerButton: {
paddingBottom: 16,
},
container: { container: {
padding: 0, padding: 0,
paddingBottom: 32, paddingBottom: 32,
@ -243,7 +250,6 @@ const styles = StyleSheet.create({
}, },
addRelay: { addRelay: {
alignContent: 'center', alignContent: 'center',
height: '80%',
justifyContent: 'space-between', justifyContent: 'space-between',
}, },
relayActions: { relayActions: {

View File

@ -1,4 +1,4 @@
import React, { useContext, useEffect, useState } from 'react' import React, { useContext, useEffect, useMemo, useState } from 'react'
import { StyleSheet, View } from 'react-native' import { StyleSheet, View } from 'react-native'
import { AppContext } from '../../Contexts/AppContext' import { AppContext } from '../../Contexts/AppContext'
import { Event } from '../../lib/nostr/Events' import { Event } from '../../lib/nostr/Events'
@ -9,15 +9,17 @@ import { Note } from '../../Functions/DatabaseFunctions/Notes'
import { getETags, getTaggedPubKeys } from '../../Functions/RelayFunctions/Events' import { getETags, getTaggedPubKeys } from '../../Functions/RelayFunctions/Events'
import { getUsers, User } from '../../Functions/DatabaseFunctions/Users' import { getUsers, User } from '../../Functions/DatabaseFunctions/Users'
import { formatPubKey } from '../../Functions/RelayFunctions/Users' import { formatPubKey } from '../../Functions/RelayFunctions/Users'
import { launchImageLibrary } from 'react-native-image-picker' import { Asset, launchImageLibrary } from 'react-native-image-picker'
import { import {
Button, Button,
Card,
IconButton, IconButton,
Snackbar, Snackbar,
Switch, Switch,
Text, Text,
TextInput, TextInput,
TouchableRipple, TouchableRipple,
useTheme,
} from 'react-native-paper' } from 'react-native-paper'
import { UserContext } from '../../Contexts/UserContext' import { UserContext } from '../../Contexts/UserContext'
import { goBack } from '../../lib/Navigation' import { goBack } from '../../lib/Navigation'
@ -25,13 +27,15 @@ import { Kind } from 'nostr-tools'
import ProfileData from '../../Components/ProfileData' import ProfileData from '../../Components/ProfileData'
import NoteCard from '../../Components/NoteCard' import NoteCard from '../../Components/NoteCard'
import { imageHostingServices } from '../../Constants/Services' import { imageHostingServices } from '../../Constants/Services'
import RBSheet from 'react-native-raw-bottom-sheet'
interface SendPageProps { interface SendPageProps {
route: { params: { note: Note; type?: 'reply' | 'repost' } | undefined } route: { params: { note: Note; type?: 'reply' | 'repost' } | undefined }
} }
export const SendPage: React.FC<SendPageProps> = ({ route }) => { export const SendPage: React.FC<SendPageProps> = ({ route }) => {
const { database, imageHostingService } = useContext(AppContext) const theme = useTheme()
const { database, getImageHostingService } = useContext(AppContext)
const { publicKey } = useContext(UserContext) const { publicKey } = useContext(UserContext)
const { relayPool, lastConfirmationtId } = useContext(RelayPoolContext) const { relayPool, lastConfirmationtId } = useContext(RelayPoolContext)
const { t } = useTranslation('common') const { t } = useTranslation('common')
@ -43,7 +47,10 @@ export const SendPage: React.FC<SendPageProps> = ({ route }) => {
const [userSuggestions, setUserSuggestions] = useState<User[]>([]) const [userSuggestions, setUserSuggestions] = useState<User[]>([])
const [userMentions, setUserMentions] = useState<User[]>([]) const [userMentions, setUserMentions] = useState<User[]>([])
const [isSending, setIsSending] = useState<boolean>(false) const [isSending, setIsSending] = useState<boolean>(false)
const [imageUpload, setImageUpload] = useState<Asset>()
const note = React.useMemo(() => route.params?.note, []) const note = React.useMemo(() => route.params?.note, [])
const [imageHostingService] = useState<string>(getImageHostingService())
const bottomSheetImageRef = React.useRef<RBSheet>(null)
useEffect(() => { useEffect(() => {
if (isSending) goBack() if (isSending) goBack()
@ -76,32 +83,44 @@ export const SendPage: React.FC<SendPageProps> = ({ route }) => {
return `@${user.name ?? formatPubKey(user.id)}` return `@${user.name ?? formatPubKey(user.id)}`
} }
const onUploadImage: () => void = async () => { const getImage: () => void = () => {
launchImageLibrary({ selectionLimit: 1, quality: 0, mediaType: 'photo' }, async (result) => { launchImageLibrary({ selectionLimit: 1, mediaType: 'photo' }, async (result) => {
const assets = result?.assets const assets = result?.assets
if (assets && assets.length > 0) { if (assets && assets.length > 0) {
const file = assets[0] const file = assets[0]
if (file.uri && file.type && file.fileName) { if (file.uri && file.type && file.fileName) {
imageHostingServices[imageHostingService] setImageUpload(file)
.sendFunction(file.uri, file.type, file.fileName) bottomSheetImageRef.current?.open()
.then((imageUri) => {
setShowNotification('imageUploaded')
setUploadingFile(false)
setContent((prev) => `${prev}\n\n${imageUri}`)
})
.catch(() => {
setShowNotification('imageUploadErro')
setUploadingFile(false)
})
} else { } else {
setUploadingFile(false) setUploadingFile(false)
setShowNotification('imageUploadErro')
} }
} else { } else {
setUploadingFile(false) setUploadingFile(false)
setShowNotification('imageUploadErro')
} }
}) })
} }
const uploadImage: () => void = async () => {
if (imageUpload?.uri && imageUpload.type && imageUpload.fileName) {
imageHostingServices[imageHostingService]
.sendFunction(imageUpload.uri, imageUpload.type, imageUpload.fileName)
.then((imageUri) => {
bottomSheetImageRef.current?.close()
setUploadingFile(false)
setContent((prev) => `${prev}\n\n${imageUri}`)
setImageUpload(undefined)
setShowNotification('imageUploaded')
})
.catch(() => {
bottomSheetImageRef.current?.close()
setUploadingFile(false)
setShowNotification('imageUploadErro')
})
}
}
const onPressSend: () => void = () => { const onPressSend: () => void = () => {
if (database && publicKey) { if (database && publicKey) {
setIsSending(true) setIsSending(true)
@ -179,6 +198,21 @@ export const SendPage: React.FC<SendPageProps> = ({ route }) => {
</TouchableRipple> </TouchableRipple>
) )
const bottomSheetStyles = React.useMemo(() => {
return {
container: {
backgroundColor: theme.colors.background,
paddingTop: 16,
paddingRight: 16,
paddingBottom: 32,
paddingLeft: 16,
borderTopRightRadius: 28,
borderTopLeftRadius: 28,
height: 'auto',
},
}
}, [])
return ( return (
<> <>
<View style={[styles.textInputContainer, { paddingBottom: note ? 200 : 10 }]}> <View style={[styles.textInputContainer, { paddingBottom: note ? 200 : 10 }]}>
@ -222,7 +256,7 @@ export const SendPage: React.FC<SendPageProps> = ({ route }) => {
icon='image-outline' icon='image-outline'
size={25} size={25}
style={styles.imageButton} style={styles.imageButton}
onPress={onUploadImage} onPress={getImage}
disabled={uploadingFile} disabled={uploadingFile}
/> />
</View> </View>
@ -239,6 +273,31 @@ export const SendPage: React.FC<SendPageProps> = ({ route }) => {
</View> </View>
)} )}
</View> </View>
<RBSheet ref={bottomSheetImageRef} closeOnDragDown={true} customStyles={bottomSheetStyles}>
<Card style={styles.imageUploadPreview}>
{imageUpload && (
<Card.Cover source={{ uri: imageUpload?.uri ?? '' }} resizeMode='contain' />
)}
</Card>
<Text>{t('sendPage.poweredBy', { uri: imageHostingServices[imageHostingService].uri })}</Text>
<Button
style={styles.buttonSpacer}
mode='contained'
onPress={uploadImage}
loading={uploadingFile}
>
{t('sendPage.uploadImage')}
</Button>
<Button
mode='outlined'
onPress={() => {
bottomSheetImageRef.current?.close()
setImageUpload(undefined)
}}
>
{t('sendPage.cancel')}
</Button>
</RBSheet>
{showNotification && ( {showNotification && (
<Snackbar <Snackbar
style={styles.snackbar} style={styles.snackbar}
@ -324,6 +383,14 @@ const styles = StyleSheet.create({
paddingTop: 4, paddingTop: 4,
paddingLeft: 5, paddingLeft: 5,
}, },
imageUploadPreview: {
marginTop: 16,
marginBottom: 16,
},
buttonSpacer: {
marginTop: 16,
marginBottom: 16,
},
}) })
export default SendPage export default SendPage

View File

@ -53,6 +53,7 @@
"react-native-screens": "^3.19.0", "react-native-screens": "^3.19.0",
"react-native-securerandom": "^1.0.1", "react-native-securerandom": "^1.0.1",
"react-native-sensitive-info": "^5.5.8", "react-native-sensitive-info": "^5.5.8",
"react-native-share": "^8.1.0",
"react-native-svg": "^13.7.0", "react-native-svg": "^13.7.0",
"react-native-tab-view": "^3.3.4", "react-native-tab-view": "^3.3.4",
"react-native-vector-icons": "^9.2.0", "react-native-vector-icons": "^9.2.0",

View File

@ -7287,6 +7287,11 @@ react-native-sensitive-info@^5.5.8:
resolved "https://registry.yarnpkg.com/react-native-sensitive-info/-/react-native-sensitive-info-5.5.8.tgz#6ebb67eed83d1c2867bd435630ef2c41eef204ed" resolved "https://registry.yarnpkg.com/react-native-sensitive-info/-/react-native-sensitive-info-5.5.8.tgz#6ebb67eed83d1c2867bd435630ef2c41eef204ed"
integrity sha512-p99oaEW4QG1RdUNrkvd/c6Qdm856dQw/Rk81f9fA6Y3DlPs6ADNdU+jbPuTz3CcOUJwuKBDNenX6LR9KfmGFEg== integrity sha512-p99oaEW4QG1RdUNrkvd/c6Qdm856dQw/Rk81f9fA6Y3DlPs6ADNdU+jbPuTz3CcOUJwuKBDNenX6LR9KfmGFEg==
react-native-share@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/react-native-share/-/react-native-share-8.1.0.tgz#34c9977e5aa49254b191f19d779bb4cff43fe2da"
integrity sha512-gME+6+FkQQ5/Ss4ulPjxwtgyZsF/YqBvG3qIVWN1urUhFFG2m2kycrNB0fPLLZy517/G6aDyUMioVZtPQArRHQ==
react-native-svg@^13.7.0: react-native-svg@^13.7.0:
version "13.7.0" version "13.7.0"
resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-13.7.0.tgz#be2ffb935e996762543dd7376bdc910722f7a43c" resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-13.7.0.tgz#be2ffb935e996762543dd7376bdc910722f7a43c"