clipboard search

This commit is contained in:
KoalaSat 2023-01-31 18:56:29 +01:00
parent 25de204b0c
commit 3b4e7c62a5
No known key found for this signature in database
GPG Key ID: 2F7F61C6146AB157
11 changed files with 202 additions and 88 deletions

View File

@ -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,

View File

@ -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,

View File

@ -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()

View File

@ -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

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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

View File

@ -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'

View File

@ -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",

View File

@ -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"