mirror of
https://github.com/KoalaSat/nostros.git
synced 2024-09-29 06:30:47 +00:00
Upload images (#215)
This commit is contained in:
commit
945446b5f0
25
frontend/Constants/Services/index.ts
Normal file
25
frontend/Constants/Services/index.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { nostrBuildUpload } from '../../Functions/ServicesFunctions/NostrBuildUpload'
|
||||||
|
import { voidCatUpload } from '../../Functions/ServicesFunctions/VoidCatUpload'
|
||||||
|
|
||||||
|
export const imageHostingServices: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
uri: string
|
||||||
|
uploadUrl: string
|
||||||
|
donation: string
|
||||||
|
sendFunction: (fileUri: string, fileType: string, filename: string) => Promise<string | null>
|
||||||
|
}
|
||||||
|
> = {
|
||||||
|
voidCat: {
|
||||||
|
uri: 'https://void.cat',
|
||||||
|
uploadUrl: 'https://void.cat/upload',
|
||||||
|
donation: 'https://void.cat/donate',
|
||||||
|
sendFunction: voidCatUpload,
|
||||||
|
},
|
||||||
|
nostrBuild: {
|
||||||
|
uri: 'https://nostr.build',
|
||||||
|
uploadUrl: 'https://nostr.build/upload.php',
|
||||||
|
donation: 'https://nostr.build',
|
||||||
|
sendFunction: nostrBuildUpload,
|
||||||
|
},
|
||||||
|
}
|
@ -4,6 +4,8 @@ import { initDatabase } from '../Functions/DatabaseFunctions'
|
|||||||
import SInfo from 'react-native-sensitive-info'
|
import SInfo from 'react-native-sensitive-info'
|
||||||
import { Linking, StyleSheet } from 'react-native'
|
import { Linking, StyleSheet } from 'react-native'
|
||||||
import { Text } from 'react-native-paper'
|
import { Text } from 'react-native-paper'
|
||||||
|
import { Config } from '../Pages/ConfigPage'
|
||||||
|
import { imageHostingServices } from '../Constants/Services'
|
||||||
|
|
||||||
export interface AppContextProps {
|
export interface AppContextProps {
|
||||||
init: () => void
|
init: () => void
|
||||||
@ -16,6 +18,8 @@ export interface AppContextProps {
|
|||||||
showSensitive: boolean
|
showSensitive: boolean
|
||||||
setShowSensitive: (showPublicImages: boolean) => void
|
setShowSensitive: (showPublicImages: boolean) => void
|
||||||
satoshi: 'kebab' | 'sats'
|
satoshi: 'kebab' | 'sats'
|
||||||
|
imageHostingService: string
|
||||||
|
setImageHostingService: (imageHostingService: string) => void
|
||||||
setSatoshi: (showPublicImages: 'kebab' | 'sats') => void
|
setSatoshi: (showPublicImages: 'kebab' | 'sats') => void
|
||||||
getSatoshiSymbol: (fontSize?: number) => JSX.Element
|
getSatoshiSymbol: (fontSize?: number) => JSX.Element
|
||||||
}
|
}
|
||||||
@ -36,6 +40,8 @@ export const initialAppContext: AppContextProps = {
|
|||||||
setShowSensitive: () => {},
|
setShowSensitive: () => {},
|
||||||
satoshi: 'kebab',
|
satoshi: 'kebab',
|
||||||
setSatoshi: () => {},
|
setSatoshi: () => {},
|
||||||
|
imageHostingService: Object.keys(imageHostingServices)[0],
|
||||||
|
setImageHostingService: () => {},
|
||||||
getSatoshiSymbol: () => <></>,
|
getSatoshiSymbol: () => <></>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,6 +50,9 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E
|
|||||||
initialAppContext.showPublicImages,
|
initialAppContext.showPublicImages,
|
||||||
)
|
)
|
||||||
const [showSensitive, setShowSensitive] = React.useState<boolean>(initialAppContext.showSensitive)
|
const [showSensitive, setShowSensitive] = React.useState<boolean>(initialAppContext.showSensitive)
|
||||||
|
const [imageHostingService, setImageHostingService] = React.useState<string>(
|
||||||
|
initialAppContext.imageHostingService,
|
||||||
|
)
|
||||||
const [notificationSeenAt, setNotificationSeenAt] = React.useState<number>(0)
|
const [notificationSeenAt, setNotificationSeenAt] = React.useState<number>(0)
|
||||||
const [satoshi, setSatoshi] = React.useState<'kebab' | 'sats'>(initialAppContext.satoshi)
|
const [satoshi, setSatoshi] = React.useState<'kebab' | 'sats'>(initialAppContext.satoshi)
|
||||||
const [database, setDatabase] = useState<QuickSQLiteConnection | null>(null)
|
const [database, setDatabase] = useState<QuickSQLiteConnection | null>(null)
|
||||||
@ -64,10 +73,14 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E
|
|||||||
setShowSensitive(config.show_sensitive ?? initialAppContext.showSensitive)
|
setShowSensitive(config.show_sensitive ?? initialAppContext.showSensitive)
|
||||||
setSatoshi(config.satoshi)
|
setSatoshi(config.satoshi)
|
||||||
setNotificationSeenAt(config.last_notification_seen_at ?? 0)
|
setNotificationSeenAt(config.last_notification_seen_at ?? 0)
|
||||||
|
setImageHostingService(
|
||||||
|
config.image_hosting_service ?? initialAppContext.imageHostingService,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
show_public_images: initialAppContext.showPublicImages,
|
show_public_images: initialAppContext.showPublicImages,
|
||||||
show_sensitive: initialAppContext.showSensitive,
|
show_sensitive: initialAppContext.showSensitive,
|
||||||
|
image_hosting_service: initialAppContext.imageHostingService,
|
||||||
satoshi: initialAppContext.satoshi,
|
satoshi: initialAppContext.satoshi,
|
||||||
last_notification_seen_at: 0,
|
last_notification_seen_at: 0,
|
||||||
last_pets_at: 0,
|
last_pets_at: 0,
|
||||||
@ -93,6 +106,8 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E
|
|||||||
return (
|
return (
|
||||||
<AppContext.Provider
|
<AppContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
imageHostingService,
|
||||||
|
setImageHostingService,
|
||||||
init,
|
init,
|
||||||
loadingDb,
|
loadingDb,
|
||||||
database,
|
database,
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
import { imageHostingServices } from '../../../Constants/Services'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
export const nostrBuildUpload: (
|
||||||
|
fileUri: string,
|
||||||
|
fileType: string,
|
||||||
|
fileName: string,
|
||||||
|
) => Promise<string | null> = async (fileUri, fileType, fileName) => {
|
||||||
|
return await new Promise<string | null>((resolve, reject) => {
|
||||||
|
const formdata = new FormData()
|
||||||
|
formdata.append('fileToUpload', {
|
||||||
|
uri: fileUri,
|
||||||
|
name: fileName,
|
||||||
|
type: fileType,
|
||||||
|
})
|
||||||
|
formdata.append('submit', 'Upload Image')
|
||||||
|
const headers = {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
}
|
||||||
|
axios
|
||||||
|
.post(imageHostingServices.nostrBuild.uploadUrl, formdata, {
|
||||||
|
headers,
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
const regExp = /(https:\/\/nostr.build\/i\/nostr.build.*)<\/b>/
|
||||||
|
const imageUrl: string = response.data.match(regExp)[0].slice(0, -4)
|
||||||
|
resolve(imageUrl)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
reject(new Error('Error uploading image'))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
39
frontend/Functions/ServicesFunctions/VoidCatUpload/index.ts
Normal file
39
frontend/Functions/ServicesFunctions/VoidCatUpload/index.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// Thanks to v0l/snort for the nice code!
|
||||||
|
// https://github.com/v0l/snort/blob/39fbe3b10f94b7542df01fb085e4f164aab15fca/src/Feed/VoidUpload.ts
|
||||||
|
|
||||||
|
import { imageHostingServices } from '../../../Constants/Services'
|
||||||
|
import ReactNativeBlobUtil from 'react-native-blob-util'
|
||||||
|
|
||||||
|
export const voidCatUpload: (
|
||||||
|
fileUri: string,
|
||||||
|
fileType: string,
|
||||||
|
fileName: string,
|
||||||
|
) => Promise<string | null> = async (fileUri, fileType, fileName) => {
|
||||||
|
const digest = await ReactNativeBlobUtil.fs.hash(fileUri, 'sha256')
|
||||||
|
return await new Promise<string | null>((resolve, reject) => {
|
||||||
|
ReactNativeBlobUtil.fetch(
|
||||||
|
'POST',
|
||||||
|
imageHostingServices.voidCat.uploadUrl,
|
||||||
|
{
|
||||||
|
'Content-Type': 'application/octet-stream',
|
||||||
|
'V-Content-Type': fileType,
|
||||||
|
'V-Filename': fileName,
|
||||||
|
'V-Full-Digest': digest,
|
||||||
|
'V-Description': 'Uploaded from Nostros https://github.com/KoalaSat/nostros',
|
||||||
|
'V-Strip-Metadata': 'true',
|
||||||
|
},
|
||||||
|
ReactNativeBlobUtil.wrap(fileUri),
|
||||||
|
)
|
||||||
|
.then((repsp) => JSON.parse(repsp.data))
|
||||||
|
.then((data) => {
|
||||||
|
if (data.ok) {
|
||||||
|
resolve(`${imageHostingServices.voidCat.uri}/d/${data.file.id}.png`)
|
||||||
|
} else {
|
||||||
|
reject(new Error('Error uploading image'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
reject(new Error('Error uploading image'))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
@ -46,7 +46,9 @@
|
|||||||
"isContact": "Following",
|
"isContact": "Following",
|
||||||
"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}}",
|
||||||
|
"imageUploadErro": "There was an error while trying to upload your file"
|
||||||
},
|
},
|
||||||
"menuItems": {
|
"menuItems": {
|
||||||
"relays": "Relays",
|
"relays": "Relays",
|
||||||
@ -61,7 +63,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",
|
||||||
|
"imageHostingService": "Image hosting service"
|
||||||
},
|
},
|
||||||
"noteCard": {
|
"noteCard": {
|
||||||
"answering": "Answer to {{pubkey}}",
|
"answering": "Answer to {{pubkey}}",
|
||||||
|
@ -61,7 +61,8 @@
|
|||||||
"configPage": {
|
"configPage": {
|
||||||
"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"
|
||||||
},
|
},
|
||||||
"noteCard": {
|
"noteCard": {
|
||||||
"answering": "Responder a {{pubkey}}",
|
"answering": "Responder a {{pubkey}}",
|
||||||
|
@ -61,7 +61,8 @@
|
|||||||
"configPage": {
|
"configPage": {
|
||||||
"showPublicImages": "Afficher les images dans le flux global",
|
"showPublicImages": "Afficher les images dans le flux global",
|
||||||
"showSensitive": "Montrer les notes sensibles",
|
"showSensitive": "Montrer les notes sensibles",
|
||||||
"satoshi": "Symbole de satoshi"
|
"satoshi": "Symbole de satoshi",
|
||||||
|
"imageHostingService": "Image hosting service"
|
||||||
},
|
},
|
||||||
"noteCard": {
|
"noteCard": {
|
||||||
"answering": "Répondre à {{pubkey}}",
|
"answering": "Répondre à {{pubkey}}",
|
||||||
|
@ -56,7 +56,8 @@
|
|||||||
"followers": "{{followers}} followers",
|
"followers": "{{followers}} followers",
|
||||||
"configuration": "Настройки",
|
"configuration": "Настройки",
|
||||||
"about": "Подроблее",
|
"about": "Подроблее",
|
||||||
"logout": "Выйти"
|
"logout": "Выйти",
|
||||||
|
"imageHostingService": "Хостинг изображений"
|
||||||
},
|
},
|
||||||
"configPage": {
|
"configPage": {
|
||||||
"showPublicImages": "Show images on public feed",
|
"showPublicImages": "Show images on public feed",
|
||||||
|
@ -5,7 +5,16 @@ import { Divider, List, Switch, useTheme } from 'react-native-paper'
|
|||||||
import SInfo from 'react-native-sensitive-info'
|
import SInfo from 'react-native-sensitive-info'
|
||||||
import RBSheet from 'react-native-raw-bottom-sheet'
|
import RBSheet from 'react-native-raw-bottom-sheet'
|
||||||
import { AppContext } from '../../Contexts/AppContext'
|
import { AppContext } from '../../Contexts/AppContext'
|
||||||
import { Config } from '../../Functions/DatabaseFunctions/Config'
|
import { imageHostingServices } from '../../Constants/Services'
|
||||||
|
|
||||||
|
export interface Config {
|
||||||
|
satoshi: 'kebab' | 'sats'
|
||||||
|
show_public_images: boolean
|
||||||
|
show_sensitive: boolean
|
||||||
|
last_notification_seen_at: number
|
||||||
|
last_pets_at: number
|
||||||
|
image_hosting_service: string
|
||||||
|
}
|
||||||
|
|
||||||
export const ConfigPage: React.FC = () => {
|
export const ConfigPage: React.FC = () => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
@ -18,12 +27,15 @@ export const ConfigPage: React.FC = () => {
|
|||||||
setShowSensitive,
|
setShowSensitive,
|
||||||
satoshi,
|
satoshi,
|
||||||
setSatoshi,
|
setSatoshi,
|
||||||
|
imageHostingService,
|
||||||
|
setImageHostingService,
|
||||||
} = React.useContext(AppContext)
|
} = React.useContext(AppContext)
|
||||||
const bottomSheetRef = React.useRef<RBSheet>(null)
|
const bottomSheetSatoshiRef = React.useRef<RBSheet>(null)
|
||||||
|
const bottomSheetImageHostingRef = React.useRef<RBSheet>(null)
|
||||||
|
|
||||||
React.useEffect(() => {}, [showPublicImages, showSensitive, satoshi])
|
React.useEffect(() => {}, [showPublicImages, showSensitive, satoshi])
|
||||||
|
|
||||||
const createOptions = React.useMemo(() => {
|
const satoshiOptions = React.useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
key: 1,
|
key: 1,
|
||||||
@ -35,7 +47,7 @@ export const ConfigPage: React.FC = () => {
|
|||||||
config.satoshi = 'kebab'
|
config.satoshi = 'kebab'
|
||||||
SInfo.setItem('config', JSON.stringify(config), {})
|
SInfo.setItem('config', JSON.stringify(config), {})
|
||||||
})
|
})
|
||||||
bottomSheetRef.current?.close()
|
bottomSheetSatoshiRef.current?.close()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -48,12 +60,30 @@ export const ConfigPage: React.FC = () => {
|
|||||||
config.satoshi = 'sats'
|
config.satoshi = 'sats'
|
||||||
SInfo.setItem('config', JSON.stringify(config), {})
|
SInfo.setItem('config', JSON.stringify(config), {})
|
||||||
})
|
})
|
||||||
bottomSheetRef.current?.close()
|
bottomSheetSatoshiRef.current?.close()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const imageHostingOptions = React.useMemo(() => {
|
||||||
|
return Object.keys(imageHostingServices).map((service, index) => {
|
||||||
|
return {
|
||||||
|
key: index,
|
||||||
|
title: <Text>{imageHostingServices[service].uri}</Text>,
|
||||||
|
onPress: () => {
|
||||||
|
setImageHostingService(service)
|
||||||
|
SInfo.getItem('config', {}).then((result) => {
|
||||||
|
const config: Config = JSON.parse(result)
|
||||||
|
config.image_hosting_service = service
|
||||||
|
SInfo.setItem('config', JSON.stringify(config), {})
|
||||||
|
})
|
||||||
|
bottomSheetImageHostingRef.current?.close()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
const bottomSheetStyles = React.useMemo(() => {
|
const bottomSheetStyles = React.useMemo(() => {
|
||||||
return {
|
return {
|
||||||
container: {
|
container: {
|
||||||
@ -105,12 +135,30 @@ export const ConfigPage: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
<List.Item
|
<List.Item
|
||||||
title={t('configPage.satoshi')}
|
title={t('configPage.satoshi')}
|
||||||
onPress={() => bottomSheetRef.current?.open()}
|
onPress={() => bottomSheetSatoshiRef.current?.open()}
|
||||||
right={() => getSatoshiSymbol(25)}
|
right={() => getSatoshiSymbol(25)}
|
||||||
/>
|
/>
|
||||||
<RBSheet ref={bottomSheetRef} closeOnDragDown={true} customStyles={bottomSheetStyles}>
|
<List.Item
|
||||||
|
title={t('configPage.imageHostingService')}
|
||||||
|
onPress={() => bottomSheetImageHostingRef.current?.open()}
|
||||||
|
right={() => <Text>{imageHostingServices[imageHostingService].uri}</Text>}
|
||||||
|
/>
|
||||||
|
<RBSheet ref={bottomSheetSatoshiRef} closeOnDragDown={true} customStyles={bottomSheetStyles}>
|
||||||
<FlatList
|
<FlatList
|
||||||
data={createOptions}
|
data={satoshiOptions}
|
||||||
|
renderItem={({ item }) => {
|
||||||
|
return <List.Item key={item.key} title={item.title} onPress={item.onPress} />
|
||||||
|
}}
|
||||||
|
ItemSeparatorComponent={Divider}
|
||||||
|
/>
|
||||||
|
</RBSheet>
|
||||||
|
<RBSheet
|
||||||
|
ref={bottomSheetImageHostingRef}
|
||||||
|
closeOnDragDown={true}
|
||||||
|
customStyles={bottomSheetStyles}
|
||||||
|
>
|
||||||
|
<FlatList
|
||||||
|
data={imageHostingOptions}
|
||||||
renderItem={({ item }) => {
|
renderItem={({ item }) => {
|
||||||
return <List.Item key={item.key} title={item.title} onPress={item.onPress} />
|
return <List.Item key={item.key} title={item.title} onPress={item.onPress} />
|
||||||
}}
|
}}
|
||||||
|
@ -6,6 +6,7 @@ import { UserContext } from '../../Contexts/UserContext'
|
|||||||
import { getUsers, User } from '../../Functions/DatabaseFunctions/Users'
|
import { getUsers, User } from '../../Functions/DatabaseFunctions/Users'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import getUnixTime from 'date-fns/getUnixTime'
|
import getUnixTime from 'date-fns/getUnixTime'
|
||||||
|
import debounce from 'lodash.debounce'
|
||||||
import { StyleSheet, View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
import Logo from '../../Components/Logo'
|
import Logo from '../../Components/Logo'
|
||||||
import { Button, Text, useTheme } from 'react-native-paper'
|
import { Button, Text, useTheme } from 'react-native-paper'
|
||||||
@ -23,14 +24,17 @@ export const ProfileLoadPage: React.FC = () => {
|
|||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
React.useCallback(() => {
|
React.useCallback(() => {
|
||||||
loadMeta()
|
debounce(() => {
|
||||||
loadPets()
|
loadMeta()
|
||||||
|
loadPets()
|
||||||
|
}, 500)
|
||||||
|
|
||||||
return () => relayPool?.unsubscribe(['profile-load-notes', 'profile-load-meta-pets'])
|
return () => relayPool?.unsubscribe(['profile-load-notes', 'profile-load-meta-pets'])
|
||||||
}, []),
|
}, []),
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
loadMeta()
|
||||||
loadPets()
|
loadPets()
|
||||||
reloadUser()
|
reloadUser()
|
||||||
if (name) {
|
if (name) {
|
||||||
|
@ -9,23 +9,35 @@ 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 { Button, Switch, Text, TextInput, TouchableRipple } from 'react-native-paper'
|
import { launchImageLibrary } from 'react-native-image-picker'
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
IconButton,
|
||||||
|
Snackbar,
|
||||||
|
Switch,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
TouchableRipple,
|
||||||
|
} from 'react-native-paper'
|
||||||
import { UserContext } from '../../Contexts/UserContext'
|
import { UserContext } from '../../Contexts/UserContext'
|
||||||
import { goBack } from '../../lib/Navigation'
|
import { goBack } from '../../lib/Navigation'
|
||||||
import { Kind } from 'nostr-tools'
|
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'
|
||||||
|
|
||||||
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 } = useContext(AppContext)
|
const { database, imageHostingService } = 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')
|
||||||
// state
|
// state
|
||||||
|
const [showNotification, setShowNotification] = useState<undefined | string>()
|
||||||
|
const [uploadingFile, setUploadingFile] = useState<boolean>(false)
|
||||||
const [content, setContent] = useState<string>('')
|
const [content, setContent] = useState<string>('')
|
||||||
const [contentWarning, setContentWarning] = useState<boolean>(false)
|
const [contentWarning, setContentWarning] = useState<boolean>(false)
|
||||||
const [userSuggestions, setUserSuggestions] = useState<User[]>([])
|
const [userSuggestions, setUserSuggestions] = useState<User[]>([])
|
||||||
@ -64,6 +76,32 @@ export const SendPage: React.FC<SendPageProps> = ({ route }) => {
|
|||||||
return `@${user.name ?? formatPubKey(user.id)}`
|
return `@${user.name ?? formatPubKey(user.id)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onUploadImage: () => void = async () => {
|
||||||
|
launchImageLibrary({ selectionLimit: 1, quality: 0, mediaType: 'photo' }, async (result) => {
|
||||||
|
const assets = result?.assets
|
||||||
|
if (assets && assets.length > 0) {
|
||||||
|
const file = assets[0]
|
||||||
|
if (file.uri && file.type && file.fileName) {
|
||||||
|
imageHostingServices[imageHostingService]
|
||||||
|
.sendFunction(file.uri, file.type, file.fileName)
|
||||||
|
.then((imageUri) => {
|
||||||
|
setShowNotification('imageUploaded')
|
||||||
|
setUploadingFile(false)
|
||||||
|
setContent((prev) => `${prev}\n\n${imageUri}`)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setShowNotification('imageUploadErro')
|
||||||
|
setUploadingFile(false)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setUploadingFile(false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setUploadingFile(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const onPressSend: () => void = () => {
|
const onPressSend: () => void = () => {
|
||||||
if (database && publicKey) {
|
if (database && publicKey) {
|
||||||
setIsSending(true)
|
setIsSending(true)
|
||||||
@ -178,15 +216,22 @@ export const SendPage: React.FC<SendPageProps> = ({ route }) => {
|
|||||||
// FIXME: can't find this color
|
// FIXME: can't find this color
|
||||||
<View style={{ backgroundColor: '#001C37' }}>
|
<View style={{ backgroundColor: '#001C37' }}>
|
||||||
<View style={styles.contentWarning}>
|
<View style={styles.contentWarning}>
|
||||||
<Text>{t('sendPage.contentWarning')}</Text>
|
<Text style={styles.contentWarningText}>{t('sendPage.contentWarning')}</Text>
|
||||||
<Switch value={contentWarning} onValueChange={setContentWarning} />
|
<Switch value={contentWarning} onValueChange={setContentWarning} />
|
||||||
|
<IconButton
|
||||||
|
icon='image-outline'
|
||||||
|
size={25}
|
||||||
|
style={styles.imageButton}
|
||||||
|
onPress={onUploadImage}
|
||||||
|
disabled={uploadingFile}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.send}>
|
<View style={styles.send}>
|
||||||
<Button
|
<Button
|
||||||
mode='contained'
|
mode='contained'
|
||||||
onPress={onPressSend}
|
onPress={onPressSend}
|
||||||
disabled={route.params?.type !== 'repost' && (!content || content === '')}
|
disabled={route.params?.type !== 'repost' && (!content || content === '')}
|
||||||
loading={isSending}
|
loading={isSending || uploadingFile}
|
||||||
>
|
>
|
||||||
{t('sendPage.send')}
|
{t('sendPage.send')}
|
||||||
</Button>
|
</Button>
|
||||||
@ -194,17 +239,41 @@ export const SendPage: React.FC<SendPageProps> = ({ route }) => {
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
{showNotification && (
|
||||||
|
<Snackbar
|
||||||
|
style={styles.snackbar}
|
||||||
|
visible={showNotification !== undefined}
|
||||||
|
duration={Snackbar.DURATION_SHORT}
|
||||||
|
onIconPress={() => setShowNotification(undefined)}
|
||||||
|
onDismiss={() => setShowNotification(undefined)}
|
||||||
|
>
|
||||||
|
{t(`sendPage.${showNotification}`, {
|
||||||
|
uri: imageHostingServices[imageHostingService].donation,
|
||||||
|
})}
|
||||||
|
</Snackbar>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
contentWarningText: {
|
||||||
|
marginTop: 3,
|
||||||
|
},
|
||||||
|
snackbar: {
|
||||||
|
margin: 16,
|
||||||
|
bottom: 100,
|
||||||
|
},
|
||||||
textInputContainer: {
|
textInputContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
textInput: {
|
textInput: {
|
||||||
paddingBottom: 0,
|
paddingBottom: 0,
|
||||||
},
|
},
|
||||||
|
imageButton: {
|
||||||
|
marginBottom: -13,
|
||||||
|
marginTop: -8,
|
||||||
|
},
|
||||||
noteCard: {
|
noteCard: {
|
||||||
flexDirection: 'column-reverse',
|
flexDirection: 'column-reverse',
|
||||||
paddingLeft: 16,
|
paddingLeft: 16,
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"@react-navigation/stack": "^6.3.11",
|
"@react-navigation/stack": "^6.3.11",
|
||||||
"@scure/base": "^1.1.1",
|
"@scure/base": "^1.1.1",
|
||||||
"assert": "^2.0.0",
|
"assert": "^2.0.0",
|
||||||
|
"axios": "^1.2.6",
|
||||||
"bip-schnorr": "^0.6.6",
|
"bip-schnorr": "^0.6.6",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"cipher-base": "https://github.com/KoalaSat/cipher-base",
|
"cipher-base": "https://github.com/KoalaSat/cipher-base",
|
||||||
@ -37,7 +38,9 @@
|
|||||||
"react-native": "0.70.6",
|
"react-native": "0.70.6",
|
||||||
"react-native-action-button": "^2.8.5",
|
"react-native-action-button": "^2.8.5",
|
||||||
"react-native-bidirectional-infinite-scroll": "^0.3.3",
|
"react-native-bidirectional-infinite-scroll": "^0.3.3",
|
||||||
|
"react-native-blob-util": "^0.17.1",
|
||||||
"react-native-gesture-handler": "^2.8.0",
|
"react-native-gesture-handler": "^2.8.0",
|
||||||
|
"react-native-image-picker": "^5.0.1",
|
||||||
"react-native-multithreading": "^1.1.1",
|
"react-native-multithreading": "^1.1.1",
|
||||||
"react-native-pager-view": "^6.1.2",
|
"react-native-pager-view": "^6.1.2",
|
||||||
"react-native-paper": "^5.1.3",
|
"react-native-paper": "^5.1.3",
|
||||||
|
29
yarn.lock
29
yarn.lock
@ -2464,6 +2464,15 @@ axios@^1.2.2:
|
|||||||
form-data "^4.0.0"
|
form-data "^4.0.0"
|
||||||
proxy-from-env "^1.1.0"
|
proxy-from-env "^1.1.0"
|
||||||
|
|
||||||
|
axios@^1.2.6:
|
||||||
|
version "1.2.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/axios/-/axios-1.2.6.tgz#eacb6d065baa11bad5959e7ffa0cb6745c65f392"
|
||||||
|
integrity sha512-rC/7F08XxZwjMV4iuWv+JpD3E0Ksqg9nac4IIg6RwNuF0JTeWoCo/mBNG54+tNhhI11G3/VDRbdDQTs9hGp4pQ==
|
||||||
|
dependencies:
|
||||||
|
follow-redirects "^1.15.0"
|
||||||
|
form-data "^4.0.0"
|
||||||
|
proxy-from-env "^1.1.0"
|
||||||
|
|
||||||
babel-core@^7.0.0-bridge.0:
|
babel-core@^7.0.0-bridge.0:
|
||||||
version "7.0.0-bridge.0"
|
version "7.0.0-bridge.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece"
|
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece"
|
||||||
@ -2596,6 +2605,11 @@ balanced-match@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||||
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
||||||
|
|
||||||
|
base-64@0.1.0:
|
||||||
|
version "0.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb"
|
||||||
|
integrity sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==
|
||||||
|
|
||||||
base-x@^3.0.2:
|
base-x@^3.0.2:
|
||||||
version "3.0.9"
|
version "3.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320"
|
resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320"
|
||||||
@ -4487,7 +4501,7 @@ glob-parent@^6.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-glob "^4.0.3"
|
is-glob "^4.0.3"
|
||||||
|
|
||||||
glob@^7.1.3, glob@^7.1.4:
|
glob@^7.1.3, glob@^7.1.4, glob@^7.2.3:
|
||||||
version "7.2.3"
|
version "7.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
|
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
|
||||||
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
|
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
|
||||||
@ -7152,6 +7166,14 @@ react-native-bidirectional-infinite-scroll@^0.3.3:
|
|||||||
resolved "https://registry.yarnpkg.com/react-native-bidirectional-infinite-scroll/-/react-native-bidirectional-infinite-scroll-0.3.3.tgz#31e83e30514be2eaaa889b97d01149c8a08576ec"
|
resolved "https://registry.yarnpkg.com/react-native-bidirectional-infinite-scroll/-/react-native-bidirectional-infinite-scroll-0.3.3.tgz#31e83e30514be2eaaa889b97d01149c8a08576ec"
|
||||||
integrity sha512-zxYJDjrxTkGqg83WH3fSdufg79XZ7xDDn9HdHlKo9avAcz92Rf28/ivDeUM2aOUmmboqJK8BqtVByT6cF/taYg==
|
integrity sha512-zxYJDjrxTkGqg83WH3fSdufg79XZ7xDDn9HdHlKo9avAcz92Rf28/ivDeUM2aOUmmboqJK8BqtVByT6cF/taYg==
|
||||||
|
|
||||||
|
react-native-blob-util@^0.17.1:
|
||||||
|
version "0.17.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-native-blob-util/-/react-native-blob-util-0.17.1.tgz#d9d6caaa79cd44622ff68c566ab9b448c97003b6"
|
||||||
|
integrity sha512-sel2PvprG3Y5XK89mIuezB/ROTDkr9cz9nJXxfXil12GGZpGRsOsB+e919ZEGWV/BfPFx5AVjOE67XOJ7FNLMQ==
|
||||||
|
dependencies:
|
||||||
|
base-64 "0.1.0"
|
||||||
|
glob "^7.2.3"
|
||||||
|
|
||||||
react-native-codegen@^0.70.6:
|
react-native-codegen@^0.70.6:
|
||||||
version "0.70.6"
|
version "0.70.6"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-codegen/-/react-native-codegen-0.70.6.tgz#2ce17d1faad02ad4562345f8ee7cbe6397eda5cb"
|
resolved "https://registry.yarnpkg.com/react-native-codegen/-/react-native-codegen-0.70.6.tgz#2ce17d1faad02ad4562345f8ee7cbe6397eda5cb"
|
||||||
@ -7178,6 +7200,11 @@ react-native-gradle-plugin@^0.70.3:
|
|||||||
resolved "https://registry.yarnpkg.com/react-native-gradle-plugin/-/react-native-gradle-plugin-0.70.3.tgz#cbcf0619cbfbddaa9128701aa2d7b4145f9c4fc8"
|
resolved "https://registry.yarnpkg.com/react-native-gradle-plugin/-/react-native-gradle-plugin-0.70.3.tgz#cbcf0619cbfbddaa9128701aa2d7b4145f9c4fc8"
|
||||||
integrity sha512-oOanj84fJEXUg9FoEAQomA8ISG+DVIrTZ3qF7m69VQUJyOGYyDZmPqKcjvRku4KXlEH6hWO9i4ACLzNBh8gC0A==
|
integrity sha512-oOanj84fJEXUg9FoEAQomA8ISG+DVIrTZ3qF7m69VQUJyOGYyDZmPqKcjvRku4KXlEH6hWO9i4ACLzNBh8gC0A==
|
||||||
|
|
||||||
|
react-native-image-picker@^5.0.1:
|
||||||
|
version "5.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-native-image-picker/-/react-native-image-picker-5.0.1.tgz#c9e99217396bc82a977785e39e14afb4819e8448"
|
||||||
|
integrity sha512-+poQTHOnEGrbxJnut591XA9006svFOyfPg/i5bv+fLuwoSHh5HW0E/PVhvT8lbX0Z5C108vh3DAsnrfFFnPBGw==
|
||||||
|
|
||||||
react-native-multithreading@^1.1.1:
|
react-native-multithreading@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-multithreading/-/react-native-multithreading-1.1.1.tgz#e1522ecd56115993d444a69c21bca49ca123bf4e"
|
resolved "https://registry.yarnpkg.com/react-native-multithreading/-/react-native-multithreading-1.1.1.tgz#e1522ecd56115993d444a69c21bca49ca123bf4e"
|
||||||
|
Loading…
Reference in New Issue
Block a user