Relay drawer (#240)

This commit is contained in:
KoalaSat 2023-02-03 18:29:43 +00:00 committed by GitHub
commit d20fa83dff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 539 additions and 260 deletions

View File

@ -21,6 +21,7 @@ module.exports = {
'react/prop-types': 'off',
'react/react-in-jsx-scope': 'off',
'@typescript-eslint/strict-boolean-expressions': 'off',
'@typescript-eslint/no-confusing-void-expression': 'off',
'@typescript-eslint/no-dynamic-delete': 'off',
'@typescript-eslint/no-floating-promises': 'off',
'@typescript-eslint/no-misused-promises': 'off',

View File

@ -19,7 +19,7 @@
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter android:label="filter_react_native">
<intent-filter android:label="">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

View File

@ -3,6 +3,7 @@ import { StyleSheet, View } from 'react-native'
import { Avatar as PaperAvatar, useTheme } from 'react-native-paper'
import { validImageUrl } from '../../Functions/NativeFunctions'
import FastImage from 'react-native-fast-image'
import { formatPubKey } from '../../Functions/RelayFunctions/Users'
interface NostrosAvatarProps {
pubKey?: string
@ -20,7 +21,7 @@ export const NostrosAvatar: React.FC<NostrosAvatarProps> = ({
lud06,
}) => {
const theme = useTheme()
const displayName = name && name !== '' ? name : pubKey ?? ''
const displayName = name && name !== '' ? name : formatPubKey(pubKey) ?? ''
const hasLud06 = lud06 && lud06 !== ''
const lud06IconSize = size / 2.85

View File

@ -8,7 +8,7 @@ import {
Note,
NoteRelay,
} from '../../Functions/DatabaseFunctions/Notes'
import { StyleSheet, View } from 'react-native'
import { StyleSheet, TouchableNativeFeedback, View } from 'react-native'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
import { AppContext } from '../../Contexts/AppContext'
import { t } from 'i18next'
@ -34,14 +34,12 @@ import {
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
import { REGEX_SOCKET_LINK } from '../../Constants/Relay'
import { push } from '../../lib/Navigation'
import { User } from '../../Functions/DatabaseFunctions/Users'
import { Kind } from 'nostr-tools'
import ProfileData from '../ProfileData'
import { relayToColor } from '../../Functions/NativeFunctions'
interface NoteCardProps {
note?: Note
onPressUser?: (user: User) => void
showAvatarImage?: boolean
showAnswerData?: boolean
showAction?: boolean
@ -60,14 +58,13 @@ export const NoteCard: React.FC<NoteCardProps> = ({
showActionCount = true,
showPreview = true,
showRepostPreview = true,
onPressUser = () => {},
numberOfLines,
mode = 'elevated',
}) => {
const theme = useTheme()
const { publicKey, privateKey } = React.useContext(UserContext)
const { relayPool, lastEventId } = useContext(RelayPoolContext)
const { database, showSensitive } = useContext(AppContext)
const { relayPool, lastEventId, setDisplayrelayDrawer } = useContext(RelayPoolContext)
const { database, showSensitive, setDisplayUserDrawer } = useContext(AppContext)
const [relayAdded, setRelayAdded] = useState<boolean>(false)
const [positiveReactions, setPositiveReactions] = useState<number>(0)
const [negaiveReactions, setNegativeReactions] = useState<number>(0)
@ -171,7 +168,7 @@ export const NoteCard: React.FC<NoteCardProps> = ({
) : (
<TextContent
event={note}
onPressUser={onPressUser}
onPressUser={(user) => setDisplayUserDrawer(user.id)}
showPreview={showPreview}
numberOfLines={numberOfLines}
/>
@ -266,7 +263,7 @@ export const NoteCard: React.FC<NoteCardProps> = ({
return note ? (
<Card mode={mode}>
<Card.Content style={styles.title}>
<TouchableRipple onPress={() => onPressUser({ id: note.pubkey, name: note.name })}>
<TouchableRipple onPress={() => setDisplayUserDrawer(note.pubkey)}>
<ProfileData
username={note?.name}
publicKey={note.pubkey}
@ -283,13 +280,13 @@ export const NoteCard: React.FC<NoteCardProps> = ({
<IconButton
icon='dots-vertical'
size={24}
onPress={() => onPressUser({ id: note.pubkey, name: note.name })}
onPress={() => setDisplayUserDrawer(note.pubkey)}
/>
</View>
)}
</Card.Content>
{getNoteContent()}
{showAction && !note?.blocked > 0 && (
{showAction && (
<Card.Content style={[styles.bottomActions, { borderColor: theme.colors.onSecondary }]}>
<Button
icon={() => (
@ -361,15 +358,19 @@ export const NoteCard: React.FC<NoteCardProps> = ({
)}
<Card.Content style={styles.relayList}>
{relays.map((relay, index) => (
<View
<TouchableNativeFeedback
onPress={() => setDisplayrelayDrawer(relay.relay_url)}
key={relay.relay_url}
style={[
styles.relay,
{ backgroundColor: relayToColor(relay.relay_url) },
index === 0 ? { borderBottomLeftRadius: 50 } : {},
index === relays.length - 1 ? { borderBottomRightRadius: 50 } : {},
]}
/>
>
<View
style={[
styles.relay,
{ backgroundColor: relayToColor(relay.relay_url) },
index === 0 ? { borderBottomLeftRadius: 50 } : {},
index === relays.length - 1 ? { borderBottomRightRadius: 50 } : {},
]}
/>
</TouchableNativeFeedback>
))}
</Card.Content>
</Card>
@ -388,7 +389,7 @@ const styles = StyleSheet.create({
},
relay: {
flex: 1,
height: 6,
height: 10,
},
titleUsername: {
fontWeight: 'bold',

View File

@ -23,17 +23,13 @@ import ProfileData from '../ProfileData'
import QRCode from 'react-native-qrcode-svg'
interface ProfileCardProps {
userPubKey: string
bottomSheetRef: React.RefObject<RBSheet>
showImages?: boolean
}
export const ProfileCard: React.FC<ProfileCardProps> = ({
userPubKey,
bottomSheetRef,
showImages = true,
}) => {
export const ProfileCard: React.FC<ProfileCardProps> = ({ bottomSheetRef, showImages = true }) => {
const theme = useTheme()
const { displayUserDrawer } = React.useContext(AppContext)
const bottomSheetShareRef = React.useRef<RBSheet>(null)
const { database } = React.useContext(AppContext)
const { publicKey } = React.useContext(UserContext)
@ -44,7 +40,7 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({
const [isContact, setIsContact] = React.useState<boolean>()
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(displayUserDrawer), [displayUserDrawer])
const username = React.useMemo(() => usernamePubKey(user?.name ?? '', nPub), [nPub, user])
React.useEffect(() => {
@ -52,8 +48,8 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({
}, [])
const onChangeBlockUser: () => void = () => {
if (database && blocked !== undefined) {
updateUserBlock(userPubKey, database, !blocked).then(() => {
if (database && blocked !== undefined && displayUserDrawer) {
updateUserBlock(displayUserDrawer, database, !blocked).then(() => {
setBlocked(blocked === 0 ? 1 : 0)
loadUser()
})
@ -61,8 +57,8 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({
}
const removeContact: () => void = () => {
if (relayPool && database && publicKey) {
updateUserContact(userPubKey, database, false).then(() => {
if (relayPool && database && publicKey && displayUserDrawer) {
updateUserContact(displayUserDrawer, database, false).then(() => {
populatePets(relayPool, database, publicKey)
setIsContact(false)
setShowNotification('contactRemoved')
@ -71,8 +67,8 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({
}
const addContact: () => void = () => {
if (relayPool && database && publicKey) {
updateUserContact(userPubKey, database, true).then(() => {
if (relayPool && database && publicKey && displayUserDrawer) {
updateUserContact(displayUserDrawer, database, true).then(() => {
populatePets(relayPool, database, publicKey)
setIsContact(true)
setShowNotification('contactAdded')
@ -81,14 +77,14 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({
}
const loadUser: () => void = () => {
if (database) {
getUser(userPubKey, database).then((result) => {
if (database && displayUserDrawer) {
getUser(displayUserDrawer, database).then((result) => {
if (result) {
setUser(result)
setBlocked(result.blocked)
setIsContact(result?.contact)
} else {
setUser({ id: userPubKey })
setUser({ id: displayUserDrawer })
setBlocked(0)
}
})
@ -97,7 +93,7 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({
const goToProfile: () => void = () => {
bottomSheetRef.current?.close()
push('Profile', { pubKey: userPubKey, title: username })
push('Profile', { pubKey: displayUserDrawer, title: username })
}
const bottomSheetStyles = React.useMemo(() => {
@ -123,7 +119,7 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({
<View style={styles.cardUserMain}>
<ProfileData
username={user?.name}
publicKey={user?.id ?? userPubKey}
publicKey={user?.id ?? displayUserDrawer}
validNip05={user?.valid_nip05}
nip05={user?.nip05}
lud06={user?.lnurl}
@ -151,7 +147,7 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({
</Card.Content>
</Card>
<View style={styles.mainLayout}>
{userPubKey !== publicKey && (
{displayUserDrawer !== publicKey && (
<View style={styles.actionButton}>
<IconButton
icon={isContact ? 'account-multiple-remove-outline' : 'account-multiple-plus-outline'}
@ -168,7 +164,7 @@ export const ProfileCard: React.FC<ProfileCardProps> = ({
icon='message-plus-outline'
size={28}
onPress={() => {
navigate('Conversation', { pubKey: userPubKey, title: username })
navigate('Conversation', { pubKey: displayUserDrawer, title: username })
bottomSheetRef.current?.close()
}}
/>

View File

@ -0,0 +1,292 @@
import { t } from 'i18next'
import * as React from 'react'
import { StyleSheet, Switch, View } from 'react-native'
import { Divider, IconButton, List, Snackbar, Text } from 'react-native-paper'
import { AppContext } from '../../Contexts/AppContext'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
import RBSheet from 'react-native-raw-bottom-sheet'
import { getRelay, Relay, RelayInfo } from '../../Functions/DatabaseFunctions/Relays'
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
import { relayToColor } from '../../Functions/NativeFunctions'
import axios from 'axios'
import { ScrollView } from 'react-native-gesture-handler'
import Clipboard from '@react-native-clipboard/clipboard'
import Logo from '../Logo'
interface RelayCardProps {
url?: string
bottomSheetRef: React.RefObject<RBSheet>
}
export const RelayCard: React.FC<RelayCardProps> = ({ url, bottomSheetRef }) => {
const { updateRelayItem } = React.useContext(RelayPoolContext)
const { database } = React.useContext(AppContext)
const [relay, setRelay] = React.useState<Relay>()
const [uri, setUri] = React.useState<string>()
const [relayInfo, setRelayInfo] = React.useState<RelayInfo>()
const [showNotification, setShowNotification] = React.useState<string>()
const [moreInfo, setMoreInfo] = React.useState<boolean>(false)
React.useEffect(() => {
loadRelay()
}, [])
React.useEffect(() => {
if (relay) {
setUri(relay.url.split('wss://')[1]?.split('/')[0])
}
}, [relay])
React.useEffect(() => {
if (uri && moreInfo) {
const headers = {
Accept: 'application/nostr+json',
}
axios
.get('http://' + uri, {
headers,
})
.then((response) => {
setRelayInfo(response.data)
})
.catch((e) => {
console.log(e)
})
}
}, [moreInfo])
const activeRelay: () => void = () => {
if (relay) {
const newRelay = relay
newRelay.active = 1
newRelay.global_feed = 1
updateRelayItem(newRelay).then(() => {
setShowNotification('active')
})
setRelay(newRelay)
}
}
const desactiveRelay: () => void = () => {
if (relay) {
const newRelay = relay
newRelay.active = 0
newRelay.global_feed = 0
updateRelayItem(newRelay).then(() => {
setShowNotification('desactive')
})
setRelay(newRelay)
}
}
const activeGlobalFeedRelay: () => void = () => {
if (relay) {
const newRelay = relay
newRelay.active = 1
newRelay.global_feed = 1
updateRelayItem(newRelay).then(() => {
setShowNotification('globalFeedActive')
})
setRelay(newRelay)
}
}
const desactiveGlobalFeedRelay: () => void = () => {
if (relay) {
const newRelay = relay
newRelay.global_feed = 0
updateRelayItem(relay).then(() => {
setShowNotification('globalFeedActiveUnactive')
})
setRelay(newRelay)
}
}
const loadRelay: () => void = () => {
if (database && url) {
getRelay(database, url).then((result) => {
if (result) setRelay(result)
})
}
}
return relay ? (
<View>
<View style={styles.relayDescription}>
<View style={styles.relayName}>
<MaterialCommunityIcons
style={styles.relayColor}
name='circle'
color={relayToColor(relay.url)}
/>
<Text>{relay.url.split('wss://')[1]?.split('/')[0]}</Text>
</View>
<Text>{relay.url}</Text>
<View style={styles.moreInfo}>
{!moreInfo && (
<Text style={styles.moreInfoText} onPress={() => setMoreInfo(true)}>
{t('relayCard.moreInfo')}
</Text>
)}
{moreInfo && (
<Text style={styles.moreInfoText} onPress={() => setMoreInfo(false)}>
{t('relayCard.lessInfo')}
</Text>
)}
{moreInfo && (
<>
{relayInfo ? (
<ScrollView>
<View style={styles.moreInfoItem}>
<Text variant='titleMedium'>{t('relayCard.description')}</Text>
<Text>{relayInfo?.description}</Text>
</View>
<View style={styles.moreInfoItem}>
<Text variant='titleMedium'>{t('relayCard.pubkey')}</Text>
<Text
onPress={() => {
if (relayInfo?.pubkey) {
Clipboard.setString(relayInfo?.pubkey)
setShowNotification('pubkeyCopied')
}
}}
>
{relayInfo?.pubkey}
</Text>
</View>
<View style={styles.moreInfoItem}>
<Text variant='titleMedium'>{t('relayCard.contact')}</Text>
<Text
onPress={() => {
if (relayInfo?.contact) {
Clipboard.setString(relayInfo?.contact)
setShowNotification('contactCopied')
}
}}
>
{relayInfo?.contact}
</Text>
</View>
<View style={styles.moreInfoItem}>
<Text variant='titleMedium'>{t('relayCard.supportedNips')}</Text>
<Text>{relayInfo?.supported_nips.join(', ')}</Text>
</View>
<View style={styles.moreInfoItem}>
<Text variant='titleMedium'>{t('relayCard.software')}</Text>
<Text>{relayInfo?.software}</Text>
</View>
<View style={styles.moreInfoItem}>
<Text variant='titleMedium'>{t('relayCard.version')}</Text>
<Text>{relayInfo?.version}</Text>
</View>
</ScrollView>
) : (
<View style={styles.loading}>
<Logo onlyIcon size='medium' />
<Text variant='titleLarge'>{'relayCard.obtainingInfo'}</Text>
</View>
)}
</>
)}
</View>
</View>
<Divider />
<View>
<List.Item
title={t('relayCard.globalFeed')}
right={() => (
<Switch
value={relay.global_feed !== undefined && relay.global_feed > 0}
onValueChange={() =>
relay.global_feed ? desactiveGlobalFeedRelay() : activeGlobalFeedRelay()
}
/>
)}
/>
<List.Item
title={t('relayCard.active')}
right={() => (
<Switch
value={relay.active !== undefined && relay.active > 0}
onValueChange={() => (relay.active ? desactiveRelay() : activeRelay())}
/>
)}
/>
</View>
<Divider />
<View style={styles.actions}>
<View style={styles.actionButton}>
<IconButton
icon={'share-variant-outline'}
size={28}
onPress={() => {
Clipboard.setString(relay.url)
setShowNotification('urlCopied')
}}
/>
<Text>{t('relayCard.share')}</Text>
</View>
</View>
{showNotification && (
<Snackbar
style={styles.snackbar}
visible={showNotification !== undefined}
duration={Snackbar.DURATION_SHORT}
onIconPress={() => setShowNotification(undefined)}
onDismiss={() => setShowNotification(undefined)}
>
{t(`relayCard.notifications.${showNotification}`)}
</Snackbar>
)}
</View>
) : (
<></>
)
}
const styles = StyleSheet.create({
container: {
padding: 16,
},
loading: {
height: 372,
justifyContent: 'center',
alignItems: 'center',
},
moreInfoItem: {
paddingTop: 16,
},
relayColor: {
paddingTop: 5,
paddingRight: 8,
},
snackbar: {
marginBottom: 85,
},
moreInfo: {
paddingTop: 16,
},
moreInfoText: {
textDecorationLine: 'underline',
},
relayName: {
flexDirection: 'row',
},
relayDescription: {
padding: 16,
justifyContent: 'space-between',
},
actions: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
actionButton: {
justifyContent: 'center',
alignItems: 'center',
marginBottom: 4,
width: '33%',
},
})
export default RelayCard

View File

@ -31,6 +31,8 @@ export interface AppContextProps {
clipboardNip21?: string
setClipboardNip21: (clipboardNip21: string | undefined) => void
checkClipboard: () => void
displayUserDrawer?: string
setDisplayUserDrawer: (displayUserDrawer: string | undefined) => void
}
export interface AppContextProviderProps {
@ -46,9 +48,10 @@ export const initialAppContext: AppContextProps = {
showPublicImages: false,
setShowPublicImages: () => {},
language:
Platform.OS === 'ios'
(Platform.OS === 'ios'
? NativeModules.SettingsManager.settings.AppleLocale
: NativeModules.I18nManager.localeIdentifier,
: NativeModules.I18nManager.localeIdentifier
)?.split('_')[0] ?? 'en',
setLanguage: () => {},
showSensitive: false,
setShowSensitive: () => {},
@ -60,6 +63,7 @@ export const initialAppContext: AppContextProps = {
getImageHostingService: () => '',
getSatoshiSymbol: () => <></>,
setClipboardNip21: () => {},
setDisplayUserDrawer: () => {},
}
export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.Element => {
@ -79,6 +83,7 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E
const [loadingDb, setLoadingDb] = useState<boolean>(initialAppContext.loadingDb)
const [clipboardLoads, setClipboardLoads] = React.useState<string[]>([])
const [clipboardNip21, setClipboardNip21] = React.useState<string>()
const [displayUserDrawer, setDisplayUserDrawer] = React.useState<string>()
useEffect(() => {
const handleChange = AppState.addEventListener('change', (changedState) => {
@ -172,6 +177,8 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E
return (
<AppContext.Provider
value={{
displayUserDrawer,
setDisplayUserDrawer,
language,
setLanguage,
checkClipboard,

View File

@ -11,6 +11,8 @@ export interface RelayPoolContextProps {
relayPool?: RelayPool
setRelayPool: (relayPool: RelayPool) => void
lastEventId?: string
setDisplayrelayDrawer: (displayRelayDrawer: string | undefined) => void
displayRelayDrawer?: string
lastConfirmationtId?: string
relays: Relay[]
addRelayItem: (relay: Relay) => Promise<void>
@ -34,6 +36,7 @@ export const initialRelayPoolContext: RelayPoolContextProps = {
removeRelayItem: async () => await new Promise(() => {}),
updateRelayItem: async () => await new Promise(() => {}),
relays: [],
setDisplayrelayDrawer: () => {},
}
export const RelayPoolContextProvider = ({
@ -48,6 +51,7 @@ export const RelayPoolContextProvider = ({
const [lastEventId, setLastEventId] = useState<string>('')
const [lastConfirmationtId, setLastConfirmationId] = useState<string>('')
const [relays, setRelays] = React.useState<Relay[]>([])
const [displayRelayDrawer, setDisplayrelayDrawer] = React.useState<string>()
const changeEventIdHandler: (event: WebsocketEvent) => void = (event) => {
setLastEventId(event.eventId)
@ -151,6 +155,8 @@ export const RelayPoolContextProvider = ({
return (
<RelayPoolContext.Provider
value={{
displayRelayDrawer,
setDisplayrelayDrawer,
relayPoolReady,
relayPool,
setRelayPool,

View File

@ -8,6 +8,16 @@ export interface Relay {
global_feed?: number
}
export interface RelayInfo {
name: string
description: string
pubkey: string
contact: string
supported_nips: string[]
software: string
version: string
}
const databaseToEntity: (object: any) => Relay = (object) => {
return object as Relay
}
@ -31,6 +41,17 @@ export const getRelays: (db: QuickSQLiteConnection) => Promise<Relay[]> = async
return relays
}
export const getRelay: (db: QuickSQLiteConnection, url: string) => Promise<Relay> = async (
db,
url,
) => {
const notesQuery = 'SELECT * FROM nostros_relays WHERE url = ?;'
const resultSet = await db.execute(notesQuery, [url])
const items: object[] = getItems(resultSet)
const relays: Relay[] = items.map((object) => databaseToEntity(object))
return relays[0]
}
export const createRelay: (db: QuickSQLiteConnection, url: string) => Promise<QueryResult> = async (
db,
url,

View File

@ -152,6 +152,29 @@
"newMessages": "{{newNotesCount}} neue Nachrichten. Zum Aktualisieren wischen.",
"myFeed": "Mein Feed"
},
"relayCard": {
"moreInfo": "More info",
"lessInfo": "Less info",
"globalFeed": "Global Feed",
"active": "Active",
"share": "Share",
"pubkey": "Public key",
"contact": "Contact",
"supportedNips": "Supported NIPs",
"software": "Sowftware",
"version": "Version",
"description": "Description",
"obtainingInfo": "Obtaining relay information",
"notifications": {
"active": "Relay actived.",
"desactive": "Relay desactivated.",
"globalFeedActive": "Global feed enabled.",
"globalFeedActiveUnactive": "Global feed disabled.",
"pubkeyCopied": "Public key copied.",
"contactCopied": "Contact copied.",
"urlCopied": "URL copied."
}
},
"relaysPage": {
"relayName": "Address",
"globalFeed": "Global feed",

View File

@ -184,6 +184,29 @@
"alreadyExists": "Relay already exists."
}
},
"relayCard": {
"moreInfo": "More info",
"lessInfo": "Less info",
"globalFeed": "Global Feed",
"active": "Active",
"share": "Share",
"pubkey": "Public key",
"contact": "Contact",
"supportedNips": "Supported NIPs",
"software": "Sowftware",
"version": "Version",
"description": "Description",
"obtainingInfo": "Obtaining relay information",
"notifications": {
"active": "Relay actived.",
"desactive": "Relay desactivated.",
"globalFeedActive": "Global feed enabled.",
"globalFeedActiveUnactive": "Global feed disabled.",
"pubkeyCopied": "Public key copied.",
"contactCopied": "Contact copied.",
"urlCopied": "URL copied."
}
},
"profileConfigPage": {
"notifications": {
"nsecCopied": "Secret key copied.",

View File

@ -152,6 +152,29 @@
"newMessages": "{{newNotesCount}} notas nuevas. Desliza para refrescar.",
"myFeed": "Mi feed"
},
"relayCard": {
"moreInfo": "Más información",
"lessInfo": "Menos información",
"globalFeed": "Feed Global",
"active": "Activo",
"share": "Compartir",
"pubkey": "Clave pública",
"contact": "Contacto",
"supportedNips": "NIPs soportadas",
"software": "Sowftware",
"version": "Versión",
"description": "Descripción",
"obtainingInfo": "Obteniendo información del relay",
"notifications": {
"active": "Relay activado.",
"desactive": "Relay desactivado.",
"globalFeedActive": "Feed global activado.",
"globalFeedActiveUnactive": "Feed Global desactivado.",
"pubkeyCopied": "Clave pública copiada.",
"contactCopied": "Contactp copiado.",
"urlCopied": "URL copiada."
}
},
"relaysPage": {
"relayName": "Dirección",
"globalFeed": "Feed global",

View File

@ -151,6 +151,29 @@
"newMessages": "{{newNotesCount}} nouvelles notes. Balayez pour recharger.",
"myFeed": "Mon flux"
},
"relayCard": {
"moreInfo": "More info",
"lessInfo": "Less info",
"globalFeed": "Global Feed",
"active": "Active",
"share": "Share",
"pubkey": "Public key",
"contact": "Contact",
"supportedNips": "Supported NIPs",
"software": "Sowftware",
"version": "Version",
"description": "Description",
"obtainingInfo": "Obtaining relay information",
"notifications": {
"active": "Relay actived.",
"desactive": "Relay desactivated.",
"globalFeedActive": "Global feed enabled.",
"globalFeedActiveUnactive": "Global feed disabled.",
"pubkeyCopied": "Public key copied.",
"contactCopied": "Contact copied.",
"urlCopied": "URL copied."
}
},
"relaysPage": {
"relayName": "Address",
"globalFeed": "Global feed",

View File

@ -151,6 +151,29 @@
"newMessages": "{{newNotesCount}} notas nuevas. Desliza para refrescar.",
"myFeed": "My feed"
},
"relayCard": {
"moreInfo": "More info",
"lessInfo": "Less info",
"globalFeed": "Global Feed",
"active": "Active",
"share": "Share",
"pubkey": "Public key",
"contact": "Contact",
"supportedNips": "Supported NIPs",
"software": "Sowftware",
"version": "Version",
"description": "Description",
"obtainingInfo": "Obtaining relay information",
"notifications": {
"active": "Relay actived.",
"desactive": "Relay desactivated.",
"globalFeedActive": "Global feed enabled.",
"globalFeedActiveUnactive": "Global feed disabled.",
"pubkeyCopied": "Public key copied.",
"contactCopied": "Contact copied.",
"urlCopied": "URL copied."
}
},
"relaysPage": {
"relayName": "Address",
"globalFeed": "Global feed",

View File

@ -34,14 +34,13 @@ import {
import RBSheet from 'react-native-raw-bottom-sheet'
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
import { useFocusEffect } from '@react-navigation/native'
import ProfileCard from '../../Components/ProfileCard'
import ProfileData from '../../Components/ProfileData'
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
export const ContactsFeed: React.FC = () => {
const { t } = useTranslation('common')
const initialPageSize = 20
const { database } = useContext(AppContext)
const { database, setDisplayUserDrawer } = useContext(AppContext)
const { privateKey, publicKey, nPub } = React.useContext(UserContext)
const { relayPool, lastEventId } = useContext(RelayPoolContext)
const theme = useTheme()
@ -52,7 +51,6 @@ export const ContactsFeed: React.FC = () => {
const [followers, setFollowers] = useState<User[]>([])
const [following, setFollowing] = useState<User[]>([])
const [contactInput, setContactInput] = useState<string>()
const [profileCardPubkey, setProfileCardPubkey] = useState<string>()
const [isAddingContact, setIsAddingContact] = useState<boolean>(false)
const [showNotification, setShowNotification] = useState<undefined | string>()
const [tabKey, setTabKey] = React.useState('following')
@ -165,7 +163,7 @@ export const ContactsFeed: React.FC = () => {
return (
<TouchableRipple
onPress={() => {
setProfileCardPubkey(item.id)
setDisplayUserDrawer(item.id)
bottomSheetProfileRef.current?.open()
}}
>
@ -339,10 +337,6 @@ export const ContactsFeed: React.FC = () => {
extended={false}
/>
)}
<RBSheet ref={bottomSheetProfileRef} closeOnDragDown={true} customStyles={bottomSheetStyles}>
<ProfileCard userPubKey={profileCardPubkey ?? ''} bottomSheetRef={bottomSheetProfileRef} />
</RBSheet>
<RBSheet
ref={bottomSheetAddContactRef}
closeOnDragDown={true}

View File

@ -15,14 +15,19 @@ import NotePage from '../NotePage'
import SendPage from '../SendPage'
import ConversationPage from '../ConversationPage'
import ConfigPage from '../ConfigPage'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
import { AppContext } from '../../Contexts/AppContext'
import RelayCard from '../../Components/RelayCard'
export const HomeNavigator: React.FC = () => {
const theme = useTheme()
const { t } = useTranslation('common')
const { displayRelayDrawer, setDisplayrelayDrawer } = React.useContext(RelayPoolContext)
const { displayUserDrawer, setDisplayUserDrawer } = React.useContext(AppContext)
const bottomSheetRef = React.useRef<RBSheet>(null)
const bottomSheetProfileRef = React.useRef<RBSheet>(null)
const bottomSheetRelayRef = React.useRef<RBSheet>(null)
const Stack = React.useMemo(() => createStackNavigator(), [])
const [showProfile, setShowProfile] = React.useState<string>()
const cardStyleInterpolator = React.useMemo(
() =>
Platform.OS === 'android'
@ -49,6 +54,14 @@ export const HomeNavigator: React.FC = () => {
bottomSheetRef.current?.open()
}
React.useEffect(() => {
if (displayRelayDrawer) bottomSheetRelayRef.current?.open()
}, [displayRelayDrawer])
React.useEffect(() => {
if (displayUserDrawer) bottomSheetProfileRef.current?.open()
}, [displayUserDrawer])
return (
<>
<Stack.Navigator
@ -78,8 +91,7 @@ export const HomeNavigator: React.FC = () => {
icon='dots-vertical'
onPress={() => {
const params = route?.params as { pubKey: string }
setShowProfile(params?.pubKey ?? '')
bottomSheetProfileRef.current?.open()
setDisplayUserDrawer(params?.pubKey ?? '')
}}
/>
)}
@ -112,8 +124,21 @@ export const HomeNavigator: React.FC = () => {
<Stack.Screen name='Profile' component={ProfilePage} />
</Stack.Group>
</Stack.Navigator>
<RBSheet ref={bottomSheetProfileRef} closeOnDragDown={true} customStyles={bottomSheetStyles}>
<ProfileCard userPubKey={showProfile ?? ''} bottomSheetRef={bottomSheetProfileRef} />
<RBSheet
ref={bottomSheetProfileRef}
closeOnDragDown={true}
customStyles={bottomSheetStyles}
onClose={() => setDisplayUserDrawer(undefined)}
>
<ProfileCard bottomSheetRef={bottomSheetProfileRef} />
</RBSheet>
<RBSheet
ref={bottomSheetRelayRef}
closeOnDragDown={true}
customStyles={bottomSheetStyles}
onClose={() => setDisplayrelayDrawer(undefined)}
>
<RelayCard url={displayRelayDrawer} bottomSheetRef={bottomSheetRelayRef} />
</RBSheet>
<RBSheet ref={bottomSheetRef} closeOnDragDown={true} customStyles={bottomSheetStyles}>
<View>

View File

@ -24,10 +24,9 @@ import { getUnixTime } from 'date-fns'
interface GlobalFeedProps {
navigation: any
setProfileCardPubKey: (profileCardPubKey: string) => void
}
export const GlobalFeed: React.FC<GlobalFeedProps> = ({ navigation, setProfileCardPubKey }) => {
export const GlobalFeed: React.FC<GlobalFeedProps> = ({ navigation }) => {
const theme = useTheme()
const { database, showPublicImages } = useContext(AppContext)
const { publicKey } = useContext(UserContext)
@ -115,9 +114,6 @@ export const GlobalFeed: React.FC<GlobalFeedProps> = ({ navigation, setProfileCa
note={item}
showActionCount={false}
showAvatarImage={showPublicImages}
onPressUser={(user) => {
setProfileCardPubKey(user.id)
}}
showPreview={showPublicImages}
/>
</View>

View File

@ -1,16 +1,13 @@
import React, { useContext, useState } from 'react'
import React, { useContext } from 'react'
import { Dimensions, ScrollView, StyleSheet, View } from 'react-native'
import { UserContext } from '../../Contexts/UserContext'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
import { AnimatedFAB, Text, TouchableRipple } from 'react-native-paper'
import RBSheet from 'react-native-raw-bottom-sheet'
import ProfileCard from '../../Components/ProfileCard'
import { useFocusEffect, useTheme } from '@react-navigation/native'
import { navigate } from '../../lib/Navigation'
import { t } from 'i18next'
import GlobalFeed from '../GlobalFeed'
import MyFeed from '../MyFeed'
import { AppContext } from '../../Contexts/AppContext'
import ReactionsFeed from '../ReactionsFeed'
import RepostsFeed from '../RepostsFeed'
@ -20,12 +17,9 @@ interface HomeFeedProps {
export const HomeFeed: React.FC<HomeFeedProps> = ({ navigation }) => {
const theme = useTheme()
const { showPublicImages } = useContext(AppContext)
const { privateKey } = useContext(UserContext)
const { relayPool } = useContext(RelayPoolContext)
const [tabKey, setTabKey] = React.useState('myFeed')
const [profileCardPubkey, setProfileCardPubKey] = useState<string>()
const bottomSheetProfileRef = React.useRef<RBSheet>(null)
useFocusEffect(
React.useCallback(() => {
@ -40,58 +34,11 @@ export const HomeFeed: React.FC<HomeFeedProps> = ({ navigation }) => {
}, []),
)
const bottomSheetStyles = React.useMemo(() => {
return {
container: {
backgroundColor: theme.colors.background,
paddingTop: 16,
paddingRight: 16,
paddingBottom: 32,
paddingLeft: 16,
borderTopRightRadius: 28,
borderTopLeftRadius: 28,
height: 'auto',
},
}
}, [])
const renderScene: Record<string, JSX.Element> = {
globalFeed: (
<GlobalFeed
navigation={navigation}
setProfileCardPubKey={(value) => {
setProfileCardPubKey(value)
bottomSheetProfileRef.current?.open()
}}
/>
),
myFeed: (
<MyFeed
navigation={navigation}
setProfileCardPubKey={(value) => {
setProfileCardPubKey(value)
bottomSheetProfileRef.current?.open()
}}
/>
),
reactions: (
<ReactionsFeed
navigation={navigation}
setProfileCardPubKey={(value) => {
setProfileCardPubKey(value)
bottomSheetProfileRef.current?.open()
}}
/>
),
reposts: (
<RepostsFeed
navigation={navigation}
setProfileCardPubKey={(value) => {
setProfileCardPubKey(value)
bottomSheetProfileRef.current?.open()
}}
/>
),
globalFeed: <GlobalFeed navigation={navigation} />,
myFeed: <MyFeed navigation={navigation} />,
reactions: <ReactionsFeed navigation={navigation} />,
reposts: <RepostsFeed navigation={navigation} />,
}
return (
@ -196,13 +143,6 @@ export const HomeFeed: React.FC<HomeFeedProps> = ({ navigation }) => {
extended={false}
/>
)}
<RBSheet ref={bottomSheetProfileRef} closeOnDragDown={true} customStyles={bottomSheetStyles}>
<ProfileCard
userPubKey={profileCardPubkey ?? ''}
bottomSheetRef={bottomSheetProfileRef}
showImages={tabKey === 'myFeed' || showPublicImages}
/>
</RBSheet>
</View>
)
}

View File

@ -24,10 +24,9 @@ import { useTranslation } from 'react-i18next'
interface MyFeedProps {
navigation: any
setProfileCardPubKey: (profileCardPubKey: string) => void
}
export const MyFeed: React.FC<MyFeedProps> = ({ navigation, setProfileCardPubKey }) => {
export const MyFeed: React.FC<MyFeedProps> = ({ navigation }) => {
const theme = useTheme()
const { t } = useTranslation('common')
const { database } = useContext(AppContext)
@ -128,12 +127,7 @@ export const MyFeed: React.FC<MyFeedProps> = ({ navigation, setProfileCardPubKey
const renderItem: ListRenderItem<Note> = ({ item }) => {
return (
<View style={styles.noteCard} key={item.id}>
<NoteCard
note={item}
onPressUser={(user) => {
setProfileCardPubKey(user.id)
}}
/>
<NoteCard note={item} />
</View>
)
}

View File

@ -9,8 +9,6 @@ import { FlashList, ListRenderItem } from '@shopify/flash-list'
import { getDirectReplies } from '../../Functions/RelayFunctions/Events'
import { AnimatedFAB, useTheme } from 'react-native-paper'
import { UserContext } from '../../Contexts/UserContext'
import RBSheet from 'react-native-raw-bottom-sheet'
import ProfileCard from '../../Components/ProfileCard'
import { navigate } from '../../lib/Navigation'
import { useFocusEffect } from '@react-navigation/native'
import { SkeletonNote } from '../../Components/SkeletonNote/SkeletonNote'
@ -26,9 +24,7 @@ export const NotePage: React.FC<NotePageProps> = ({ route }) => {
const [note, setNote] = useState<Note>()
const [replies, setReplies] = useState<Note[]>()
const [refreshing, setRefreshing] = useState(false)
const [profileCardPubkey, setProfileCardPubKey] = useState<string>()
const theme = useTheme()
const bottomSheetProfileRef = React.useRef<RBSheet>(null)
useFocusEffect(
React.useCallback(() => {
@ -98,42 +94,14 @@ export const NotePage: React.FC<NotePageProps> = ({ route }) => {
)}
</View>
<View style={styles.noteCard}>
<NoteCard
note={item}
onPressUser={(user) => {
setProfileCardPubKey(user.id)
bottomSheetProfileRef.current?.open()
}}
showAnswerData={false}
showRepostPreview={false}
/>
<NoteCard note={item} showAnswerData={false} showRepostPreview={false} />
</View>
</View>
)
const bottomSheetStyles = React.useMemo(() => {
return {
container: {
backgroundColor: theme.colors.background,
paddingTop: 16,
paddingRight: 16,
paddingBottom: 32,
paddingLeft: 16,
borderTopRightRadius: 28,
borderTopLeftRadius: 28,
height: 'auto',
},
}
}, [])
const openProfileDrawer: () => void = () => {
setProfileCardPubKey(note?.pubkey)
bottomSheetProfileRef.current?.open()
}
return note ? (
<View>
<NoteCard note={note} onPressUser={openProfileDrawer} />
<NoteCard note={note} />
<View style={[styles.list, { borderColor: theme.colors.onSecondary }]}>
<FlashList
estimatedItemSize={200}
@ -169,9 +137,6 @@ export const NotePage: React.FC<NotePageProps> = ({ route }) => {
iconMode='static'
extended={false}
/>
<RBSheet ref={bottomSheetProfileRef} closeOnDragDown={true} customStyles={bottomSheetStyles}>
<ProfileCard userPubKey={profileCardPubkey ?? ''} bottomSheetRef={bottomSheetProfileRef} />
</RBSheet>
</View>
) : (
<View>

View File

@ -14,9 +14,7 @@ import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
import { Kind } from 'nostr-tools'
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
import { UserContext } from '../../Contexts/UserContext'
import RBSheet from 'react-native-raw-bottom-sheet'
import { ActivityIndicator, Button, Text, useTheme } from 'react-native-paper'
import ProfileCard from '../../Components/ProfileCard'
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
import { useTranslation } from 'react-i18next'
import { navigate } from '../../lib/Navigation'
@ -35,9 +33,7 @@ export const NotificationsFeed: React.FC = () => {
const { lastEventId, relayPool } = useContext(RelayPoolContext)
const [pageSize, setPageSize] = useState<number>(initialPageSize)
const [notes, setNotes] = useState<Note[]>([])
const bottomSheetProfileRef = React.useRef<RBSheet>(null)
const [refreshing, setRefreshing] = useState(true)
const [profileCardPubkey, setProfileCardPubKey] = useState<string>()
useFocusEffect(
React.useCallback(() => {
@ -143,13 +139,7 @@ export const NotificationsFeed: React.FC = () => {
const renderItem: ListRenderItem<Note> = ({ item }) => {
return (
<View style={styles.noteCard} key={item.id}>
<NoteCard
note={item}
onPressUser={(user) => {
setProfileCardPubKey(user.id)
bottomSheetProfileRef.current?.open()
}}
/>
<NoteCard note={item} />
</View>
)
}
@ -160,21 +150,6 @@ export const NotificationsFeed: React.FC = () => {
}
}
const bottomSheetStyles = React.useMemo(() => {
return {
container: {
backgroundColor: theme.colors.background,
paddingTop: 16,
paddingRight: 16,
paddingBottom: 32,
paddingLeft: 16,
borderTopRightRadius: 28,
borderTopLeftRadius: 28,
height: 'auto',
},
}
}, [])
const ListEmptyComponent = React.useMemo(
() => (
<View style={styles.blank}>
@ -212,9 +187,6 @@ export const NotificationsFeed: React.FC = () => {
horizontal={false}
ListFooterComponent={<ActivityIndicator animating={true} />}
/>
<RBSheet ref={bottomSheetProfileRef} closeOnDragDown={true} customStyles={bottomSheetStyles}>
<ProfileCard userPubKey={profileCardPubkey ?? ''} bottomSheetRef={bottomSheetProfileRef} />
</RBSheet>
</View>
)
}

View File

@ -8,14 +8,7 @@ import {
View,
} from 'react-native'
import Clipboard from '@react-native-clipboard/clipboard'
import {
Surface,
Text,
IconButton,
ActivityIndicator,
useTheme,
Snackbar,
} from 'react-native-paper'
import { Surface, Text, IconButton, ActivityIndicator, Snackbar } from 'react-native-paper'
import { FlashList, ListRenderItem } from '@shopify/flash-list'
import { AppContext } from '../../Contexts/AppContext'
import { UserContext } from '../../Contexts/UserContext'
@ -29,8 +22,6 @@ import { RelayFilters } from '../../lib/nostr/RelayPool/intex'
import NoteCard from '../../Components/NoteCard'
import LnPayment from '../../Components/LnPayment'
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
import RBSheet from 'react-native-raw-bottom-sheet'
import ProfileCard from '../../Components/ProfileCard'
import { navigate } from '../../lib/Navigation'
import { useFocusEffect } from '@react-navigation/native'
import { getNpub } from '../../lib/nostr/Nip19'
@ -45,8 +36,6 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
const { publicKey } = useContext(UserContext)
const { lastEventId, relayPool } = useContext(RelayPoolContext)
const { t } = useTranslation('common')
const bottomSheetProfileRef = React.useRef<RBSheet>(null)
const theme = useTheme()
const initialPageSize = 10
const [showNotification, setShowNotification] = useState<undefined | string>()
const [notes, setNotes] = useState<Note[]>()
@ -174,25 +163,10 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
const renderItem: ListRenderItem<Note> = ({ item }) => (
<View style={styles.noteCard} key={item.id}>
<NoteCard note={item} onPressUser={() => bottomSheetProfileRef.current?.open()} />
<NoteCard note={item} />
</View>
)
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 (
<View>
<ScrollView
@ -239,7 +213,6 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
pubKey: route.params.pubKey,
title: user ? username(user) : route.params.pubKey,
})
bottomSheetProfileRef.current?.close()
}}
/>
<Text>{t('profilePage.message')}</Text>
@ -297,12 +270,6 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
</Snackbar>
)}
<LnPayment setOpen={setOpenLn} open={openLn} user={user} />
<RBSheet ref={bottomSheetProfileRef} closeOnDragDown={true} customStyles={bottomSheetStyles}>
<ProfileCard
userPubKey={route.params.pubKey ?? ''}
bottomSheetRef={bottomSheetProfileRef}
/>
</RBSheet>
</View>
)
}

View File

@ -23,13 +23,9 @@ import { getLastReaction } from '../../Functions/DatabaseFunctions/Reactions'
interface ReactionsFeedProps {
navigation: any
setProfileCardPubKey: (profileCardPubKey: string) => void
}
export const ReactionsFeed: React.FC<ReactionsFeedProps> = ({
navigation,
setProfileCardPubKey,
}) => {
export const ReactionsFeed: React.FC<ReactionsFeedProps> = ({ navigation }) => {
const theme = useTheme()
const { database } = useContext(AppContext)
const { publicKey } = useContext(UserContext)
@ -126,12 +122,7 @@ export const ReactionsFeed: React.FC<ReactionsFeedProps> = ({
const renderItem: ListRenderItem<Note> = ({ item }) => {
return (
<View style={styles.noteCard} key={item.id}>
<NoteCard
note={item}
onPressUser={(user) => {
setProfileCardPubKey(user.id)
}}
/>
<NoteCard note={item} />
</View>
)
}

View File

@ -23,10 +23,9 @@ import { getLastReaction } from '../../Functions/DatabaseFunctions/Reactions'
interface RepostsFeedProps {
navigation: any
setProfileCardPubKey: (profileCardPubKey: string) => void
}
export const RepostsFeed: React.FC<RepostsFeedProps> = ({ navigation, setProfileCardPubKey }) => {
export const RepostsFeed: React.FC<RepostsFeedProps> = ({ navigation }) => {
const theme = useTheme()
const { database } = useContext(AppContext)
const { publicKey } = useContext(UserContext)
@ -123,12 +122,7 @@ export const RepostsFeed: React.FC<RepostsFeedProps> = ({ navigation, setProfile
const renderItem: ListRenderItem<Note> = ({ item }) => {
return (
<View style={styles.noteCard} key={item.id}>
<NoteCard
note={item}
onPressUser={(user) => {
setProfileCardPubKey(user.id)
}}
/>
<NoteCard note={item} />
</View>
)
}

View File

@ -1,6 +1,7 @@
import { decode, EventPointer, npubEncode, ProfilePointer } from 'nostr-tools/nip19'
export function getNpub(key: string): string {
export function getNpub(key: string | undefined): string {
if (!key) return ''
if (isPublicKey(key)) return key
try {

View File

@ -48,7 +48,7 @@
"react-native-paper": "^5.1.3",
"react-native-parsed-text": "^0.0.22",
"react-native-qrcode-svg": "^6.1.2",
"react-native-quick-sqlite": "^6.1.1",
"react-native-quick-sqlite": "^7.0.0",
"react-native-raw-bottom-sheet": "^2.2.0",
"react-native-reanimated": "^2.14.2",
"react-native-safe-area-context": "^4.4.1",

View File

@ -7252,10 +7252,10 @@ react-native-qrcode-svg@^6.1.2:
prop-types "^15.7.2"
qrcode "^1.5.0"
react-native-quick-sqlite@^6.1.1:
version "6.1.1"
resolved "https://registry.yarnpkg.com/react-native-quick-sqlite/-/react-native-quick-sqlite-6.1.1.tgz#ba35d0a4a919a5a0962306c3fee4dc46983b0839"
integrity sha512-bVV7+mWAhWH/0iPjQmDlLHqTIkYm+j+k5pFiy+3jW91DnkH44C0tR4DSeRa6ERLz1fG/lDB1KwcBU3fOL9vFCA==
react-native-quick-sqlite@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/react-native-quick-sqlite/-/react-native-quick-sqlite-7.0.0.tgz#a8bd9344a784f391392d5ac703ebd1d6db06889f"
integrity sha512-SSJeC2ERhZz90IhCr4MZIs34+OStnfD7ziCi+XC43/YU/eymC4ko9pFmnb57e4bPRjkT891C4D4R+c2TM7QzeQ==
react-native-raw-bottom-sheet@^2.2.0:
version "2.2.0"