mirror of
https://github.com/KoalaSat/nostros.git
synced 2024-09-29 06:30:47 +00:00
clipboard search (#222)
This commit is contained in:
commit
94b293aa7a
@ -185,7 +185,7 @@ export const TextContent: React.FC<TextContentProps> = ({
|
||||
pattern: /#\[(\d+)\]/,
|
||||
},
|
||||
{ pattern: /#(\w+)/, style: styles.hashTag },
|
||||
{ pattern: /(note1)\S*/, style: styles.nip19, onPress: handleNip05NotePress },
|
||||
{ pattern: /(nevent1)\S*/, style: styles.nip19, onPress: handleNip05NotePress },
|
||||
{
|
||||
pattern: /(npub1|nprofile1)\S*/,
|
||||
style: styles.nip19,
|
||||
|
@ -1,12 +1,13 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { QuickSQLiteConnection } from 'react-native-quick-sqlite'
|
||||
import { initDatabase } from '../Functions/DatabaseFunctions'
|
||||
import SInfo from 'react-native-sensitive-info'
|
||||
import { Linking, StyleSheet } from 'react-native'
|
||||
import { AppState, Linking, StyleSheet } from 'react-native'
|
||||
import { Text } from 'react-native-paper'
|
||||
import { Config } from '../Pages/ConfigPage'
|
||||
import { imageHostingServices } from '../Constants/Services'
|
||||
import { randomInt } from '../Functions/NativeFunctions'
|
||||
import { randomInt, validNip21 } from '../Functions/NativeFunctions'
|
||||
import Clipboard from '@react-native-clipboard/clipboard'
|
||||
|
||||
export interface AppContextProps {
|
||||
init: () => void
|
||||
@ -24,6 +25,9 @@ export interface AppContextProps {
|
||||
setSatoshi: (showPublicImages: 'kebab' | 'sats') => void
|
||||
getSatoshiSymbol: (fontSize?: number) => JSX.Element
|
||||
getImageHostingService: () => string
|
||||
clipboardNip21?: string
|
||||
setClipboardNip21: (clipboardNip21: string | undefined) => void
|
||||
checkClipboard: () => void
|
||||
}
|
||||
|
||||
export interface AppContextProviderProps {
|
||||
@ -42,13 +46,17 @@ export const initialAppContext: AppContextProps = {
|
||||
setShowSensitive: () => {},
|
||||
satoshi: 'kebab',
|
||||
setSatoshi: () => {},
|
||||
checkClipboard: () => {},
|
||||
imageHostingService: Object.keys(imageHostingServices)[0],
|
||||
setImageHostingService: () => {},
|
||||
getImageHostingService: () => "",
|
||||
getImageHostingService: () => '',
|
||||
getSatoshiSymbol: () => <></>,
|
||||
setClipboardNip21: () => {},
|
||||
}
|
||||
|
||||
export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.Element => {
|
||||
const currentState = useRef(AppState.currentState)
|
||||
const [appState, setAppState] = useState(currentState.current)
|
||||
const [showPublicImages, setShowPublicImages] = React.useState<boolean>(
|
||||
initialAppContext.showPublicImages,
|
||||
)
|
||||
@ -60,6 +68,25 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E
|
||||
const [satoshi, setSatoshi] = React.useState<'kebab' | 'sats'>(initialAppContext.satoshi)
|
||||
const [database, setDatabase] = useState<QuickSQLiteConnection | null>(null)
|
||||
const [loadingDb, setLoadingDb] = useState<boolean>(initialAppContext.loadingDb)
|
||||
const [clipboardLoads, setClipboardLoads] = React.useState<string[]>([])
|
||||
const [clipboardNip21, setClipboardNip21] = React.useState<string>()
|
||||
|
||||
useEffect(() => {
|
||||
const handleChange = AppState.addEventListener('change', (changedState) => {
|
||||
currentState.current = changedState
|
||||
setAppState(currentState.current)
|
||||
})
|
||||
|
||||
return () => {
|
||||
handleChange.remove()
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (appState === 'active') {
|
||||
checkClipboard()
|
||||
}
|
||||
}, [appState])
|
||||
|
||||
const init: () => void = () => {
|
||||
const db = initDatabase()
|
||||
@ -112,11 +139,25 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E
|
||||
return Object.keys(imageHostingServices)[randomIndex - 1]
|
||||
}
|
||||
|
||||
const checkClipboard: () => void = () => {
|
||||
if (Clipboard.hasString()) {
|
||||
Clipboard.getString().then((clipboardContent) => {
|
||||
if (validNip21(clipboardContent) && !clipboardLoads.includes(clipboardContent)) {
|
||||
setClipboardLoads((prev) => [...prev, clipboardContent])
|
||||
setClipboardNip21(clipboardContent)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(init, [])
|
||||
|
||||
return (
|
||||
<AppContext.Provider
|
||||
value={{
|
||||
checkClipboard,
|
||||
clipboardNip21,
|
||||
setClipboardNip21,
|
||||
imageHostingService,
|
||||
setImageHostingService,
|
||||
getImageHostingService,
|
||||
|
@ -65,7 +65,6 @@ export const UserContextProvider = ({ children }: UserContextProviderProps): JSX
|
||||
const [lnurl, setLnurl] = useState<string>()
|
||||
const [nip05, setNip05] = useState<string>()
|
||||
const [validNip05, setValidNip05] = useState<boolean>()
|
||||
// const [clipboardLoads, setClipboardLoads] = React.useState<string[]>([])
|
||||
|
||||
const reloadUser: () => void = () => {
|
||||
if (database && publicKey) {
|
||||
@ -82,16 +81,6 @@ export const UserContextProvider = ({ children }: UserContextProviderProps): JSX
|
||||
}
|
||||
}
|
||||
|
||||
// const checkClipboard: () => void = () => {
|
||||
// if (Clipboard.hasString()) {
|
||||
// Clipboard.getString().then((clipboardContent) => {
|
||||
// if (validNip21(clipboardContent) && !clipboardLoads.includes(clipboardContent)) {
|
||||
// setClipboardLoads((prev) => [...prev, clipboardContent])
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
const logout: () => void = () => {
|
||||
if (database) {
|
||||
relayPool?.unsubscribeAll()
|
||||
|
@ -55,7 +55,7 @@ export const validBlueBirdUrl: (url: string | undefined) => boolean = (url) => {
|
||||
|
||||
export const validNip21: (string: string | undefined) => boolean = (string) => {
|
||||
if (string) {
|
||||
const regexp = /^nostr:(npub1|nprofile1|note1|nevent1)S*$/
|
||||
const regexp = /^(nostr:)?(npub1|nprofile1|nevent1|nrelay1)\S*$/
|
||||
return regexp.test(string)
|
||||
} else {
|
||||
return false
|
||||
|
@ -225,6 +225,11 @@
|
||||
"unfollow": "Following",
|
||||
"copyNPub": "Copy key"
|
||||
},
|
||||
"homePage": {
|
||||
"clipboardTitle": "Nostr key detected on your clipboard",
|
||||
"goToEvent": "Open",
|
||||
"cancel": "Cancel"
|
||||
},
|
||||
"profileCard": {
|
||||
"notifications": {
|
||||
"contactAdded": "Profile followed",
|
||||
|
@ -213,6 +213,11 @@
|
||||
"unfollow": "Siguiendo",
|
||||
"copyNPub": "Copiar clave"
|
||||
},
|
||||
"homePage": {
|
||||
"clipboardTitle": "Se ha detectado una clave Nostr en el portapapeles",
|
||||
"goToEvent": "Abrir",
|
||||
"cancel": "Cancelar"
|
||||
},
|
||||
"profileCard": {
|
||||
"notifications": {
|
||||
"contactAdded": "Siguiendo",
|
||||
|
@ -212,6 +212,11 @@
|
||||
"unfollow": "Following",
|
||||
"copyNPub": "Copy key"
|
||||
},
|
||||
"homePage": {
|
||||
"clipboardTitle": "Se ha detectado una clave Nostr en el portapapeles",
|
||||
"goToEvent": "Abrir",
|
||||
"cancel": "Отменить"
|
||||
},
|
||||
"profileCard": {
|
||||
"notifications": {
|
||||
"contactAdded": "Profile followed",
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import { Badge, TouchableRipple, useTheme } from 'react-native-paper'
|
||||
import { Badge, Button, Text, TouchableRipple, useTheme } from 'react-native-paper'
|
||||
import ContactsFeed from '../ContactsFeed'
|
||||
import ConversationsFeed from '../ConversationsFeed'
|
||||
import HomeFeed from '../HomeFeed'
|
||||
@ -12,13 +12,26 @@ import { Kind } from 'nostr-tools'
|
||||
import { getMentionNotes, getNotificationsCount } from '../../Functions/DatabaseFunctions/Notes'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import { StyleSheet } from 'react-native'
|
||||
import RBSheet from 'react-native-raw-bottom-sheet'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { navigate } from '../../lib/Navigation'
|
||||
import { decode } from 'nostr-tools/nip19'
|
||||
|
||||
export const HomePage: React.FC = () => {
|
||||
const theme = useTheme()
|
||||
const { t } = useTranslation('common')
|
||||
const { privateKey, publicKey } = React.useContext(UserContext)
|
||||
const { database, notificationSeenAt } = useContext(AppContext)
|
||||
const { database, notificationSeenAt, clipboardNip21, setClipboardNip21 } = useContext(AppContext)
|
||||
const { relayPool, lastEventId } = useContext(RelayPoolContext)
|
||||
const [newNotifications, setNewNotifications] = useState<number>(0)
|
||||
const bottomSheetClipboardRef = React.useRef<RBSheet>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (clipboardNip21) {
|
||||
console.log('clipboardNip21', clipboardNip21)
|
||||
bottomSheetClipboardRef.current?.open()
|
||||
}
|
||||
}, [clipboardNip21])
|
||||
|
||||
useEffect(() => {
|
||||
if (publicKey && database) {
|
||||
@ -45,87 +58,139 @@ export const HomePage: React.FC = () => {
|
||||
}
|
||||
}, [publicKey])
|
||||
|
||||
const goToEvent: () => void = () => {
|
||||
if (clipboardNip21) {
|
||||
const key = decode(clipboardNip21.replace('nostr:', ''))
|
||||
if (key) {
|
||||
if (key.type === 'nevent') {
|
||||
navigate('Note', { noteId: key.data })
|
||||
} else if (key.type === 'nprofile' || key.type === 'npub') {
|
||||
navigate('Profile', { pubKey: key.data })
|
||||
}
|
||||
}
|
||||
}
|
||||
bottomSheetClipboardRef.current?.close()
|
||||
}
|
||||
|
||||
const Tab = createBottomTabNavigator()
|
||||
|
||||
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 (
|
||||
<Tab.Navigator
|
||||
initialRouteName='feed'
|
||||
backBehavior='none'
|
||||
screenOptions={{
|
||||
unmountOnBlur: true,
|
||||
tabBarShowLabel: false,
|
||||
headerStyle: {
|
||||
height: 0,
|
||||
},
|
||||
tabBarStyle: {
|
||||
borderTopWidth: 0,
|
||||
height: 60,
|
||||
},
|
||||
tabBarInactiveBackgroundColor: '#001C37',
|
||||
tabBarActiveBackgroundColor: theme.colors.secondaryContainer,
|
||||
tabBarButton: (props) => <TouchableRipple {...props} />,
|
||||
}}
|
||||
>
|
||||
<Tab.Screen
|
||||
name='feed'
|
||||
component={HomeFeed}
|
||||
options={{
|
||||
tabBarIcon: ({ focused, size }) => (
|
||||
<MaterialCommunityIcons
|
||||
name={focused ? 'home' : 'home-outline'}
|
||||
size={size}
|
||||
color={theme.colors.onPrimaryContainer}
|
||||
/>
|
||||
),
|
||||
<>
|
||||
<Tab.Navigator
|
||||
initialRouteName='feed'
|
||||
backBehavior='none'
|
||||
screenOptions={{
|
||||
unmountOnBlur: true,
|
||||
tabBarShowLabel: false,
|
||||
headerStyle: {
|
||||
height: 0,
|
||||
},
|
||||
tabBarStyle: {
|
||||
borderTopWidth: 0,
|
||||
height: 60,
|
||||
},
|
||||
tabBarInactiveBackgroundColor: '#001C37',
|
||||
tabBarActiveBackgroundColor: theme.colors.secondaryContainer,
|
||||
tabBarButton: (props) => <TouchableRipple {...props} />,
|
||||
}}
|
||||
/>
|
||||
{privateKey && (
|
||||
>
|
||||
<Tab.Screen
|
||||
name='messages'
|
||||
component={ConversationsFeed}
|
||||
name='feed'
|
||||
component={HomeFeed}
|
||||
options={{
|
||||
tabBarIcon: ({ focused, size }) => (
|
||||
<MaterialCommunityIcons
|
||||
name={focused ? 'email' : 'email-outline'}
|
||||
name={focused ? 'home' : 'home-outline'}
|
||||
size={size}
|
||||
color={theme.colors.onPrimaryContainer}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Tab.Screen
|
||||
name='contacts'
|
||||
component={ContactsFeed}
|
||||
options={{
|
||||
tabBarIcon: ({ focused, size }) => (
|
||||
<MaterialCommunityIcons
|
||||
name={focused ? 'account-group' : 'account-group-outline'}
|
||||
size={size}
|
||||
color={theme.colors.onPrimaryContainer}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name='notifications'
|
||||
component={NotificationsFeed}
|
||||
options={{
|
||||
tabBarIcon: ({ focused, size }) => (
|
||||
<>
|
||||
{newNotifications > 0 && (
|
||||
<Badge style={styles.notificationBadge}>{newNotifications}</Badge>
|
||||
)}
|
||||
{privateKey && (
|
||||
<Tab.Screen
|
||||
name='messages'
|
||||
component={ConversationsFeed}
|
||||
options={{
|
||||
tabBarIcon: ({ focused, size }) => (
|
||||
<MaterialCommunityIcons
|
||||
name={focused ? 'email' : 'email-outline'}
|
||||
size={size}
|
||||
color={theme.colors.onPrimaryContainer}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Tab.Screen
|
||||
name='contacts'
|
||||
component={ContactsFeed}
|
||||
options={{
|
||||
tabBarIcon: ({ focused, size }) => (
|
||||
<MaterialCommunityIcons
|
||||
name={focused ? 'bell' : 'bell-outline'}
|
||||
name={focused ? 'account-group' : 'account-group-outline'}
|
||||
size={size}
|
||||
color={theme.colors.onPrimaryContainer}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tab.Navigator>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name='notifications'
|
||||
component={NotificationsFeed}
|
||||
options={{
|
||||
tabBarIcon: ({ focused, size }) => (
|
||||
<>
|
||||
{newNotifications > 0 && (
|
||||
<Badge style={styles.notificationBadge}>{newNotifications}</Badge>
|
||||
)}
|
||||
<MaterialCommunityIcons
|
||||
name={focused ? 'bell' : 'bell-outline'}
|
||||
size={size}
|
||||
color={theme.colors.onPrimaryContainer}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tab.Navigator>
|
||||
|
||||
<RBSheet
|
||||
ref={bottomSheetClipboardRef}
|
||||
closeOnDragDown={true}
|
||||
customStyles={bottomSheetStyles}
|
||||
>
|
||||
<Text variant='titleMedium'>{t('homePage.clipboardTitle')}</Text>
|
||||
<Text>{clipboardNip21}</Text>
|
||||
<Button style={styles.buttonSpacer} mode='contained' onPress={goToEvent}>
|
||||
{t('homePage.goToEvent')}
|
||||
</Button>
|
||||
<Button
|
||||
mode='outlined'
|
||||
onPress={() => {
|
||||
bottomSheetClipboardRef.current?.close()
|
||||
setClipboardNip21(undefined)
|
||||
}}
|
||||
>
|
||||
{t('homePage.cancel')}
|
||||
</Button>
|
||||
</RBSheet>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -135,6 +200,10 @@ const styles = StyleSheet.create({
|
||||
right: 25,
|
||||
top: 10,
|
||||
},
|
||||
buttonSpacer: {
|
||||
marginTop: 16,
|
||||
marginBottom: 16,
|
||||
},
|
||||
})
|
||||
|
||||
export default HomePage
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useContext, useEffect, useMemo, useState } from 'react'
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import { Event } from '../../lib/nostr/Events'
|
||||
|
@ -31,7 +31,7 @@
|
||||
"i18next": "^22.4.9",
|
||||
"lnurl-pay": "^2.1.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"nostr-tools": "^1.1.1",
|
||||
"nostr-tools": "^1.2.1",
|
||||
"react": "18.1.0",
|
||||
"react-content-loader": "^6.2.0",
|
||||
"react-i18next": "^12.1.4",
|
||||
|
@ -6514,10 +6514,10 @@ normalize-path@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||
|
||||
nostr-tools@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-1.1.1.tgz#2be4cd650bc0a4d20650b6cf46fee451c9f565b8"
|
||||
integrity sha512-mxgjbHR6nx2ACBNa2tBpeM/glsPWqxHPT1Kszx/XfzL+kUdi1Gm3Xz1UcaODQ2F84IFtCKNLO+aF31ZfTAhSYQ==
|
||||
nostr-tools@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-1.2.1.tgz#0871572254679744539846602ef2cd2f204d82c4"
|
||||
integrity sha512-SL0sst29mrQ7oUPGQn+NMH4yuTe69a8S4bliNpYB2IG0fDl3Cx+xSLnuCTb4nZiNalatYsA5l+751wQiDGA3+A==
|
||||
dependencies:
|
||||
"@noble/hashes" "^0.5.7"
|
||||
"@noble/secp256k1" "^1.7.0"
|
||||
|
Loading…
Reference in New Issue
Block a user