Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/scanned lote #69

Merged
merged 8 commits into from
Jan 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions app.config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
const VERSION_CODE = 12
const VERSION = '24.1.2'
const VERSION_CODE = 14
const VERSION = '24.1.4'

const versionString = `${VERSION} (${VERSION_CODE})`
export default {
'expo': {
'name': 'Lotes',
'slug': 'lotes',
'scheme': 'lotes',
'version': VERSION,
'orientation': 'portrait',
'plugins': [['react-native-nfc-manager', {includeNdefEntitlement: false}]],
Expand All @@ -27,6 +28,10 @@ export default {
},
'android': {
'versionCode': VERSION_CODE,
'adaptiveIcon': {
'foregroundImage': './assets/icon.png',
'backgroundColor': '#FFFFFF',
},
'permissions': ['android.permission.NFC'],
'package': 'com.hynjin.lotesapp',
},
Expand Down
Binary file modified assets/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"scripts": {
"start": "expo start --dev-client",
"android": "expo run:android",
"prebuild": "expo prebuild --clean",
"ios": "expo run:ios -d",
"web": "expo start --web",
"lint": "prettier . --check && eslint . --ext .js,.jsx,.ts,.tsx && tsc --noemit",
Expand All @@ -13,18 +14,19 @@
},
"dependencies": {
"@react-native-async-storage/async-storage": "1.18.2",
"@react-navigation/native": "^6.1.5",
"@react-navigation/native-stack": "^6.9.11",
"@react-navigation/native": "^6.1.9",
"@react-navigation/native-stack": "^6.9.17",
"axios": "^1.5.1",
"expo": "^49.0.0",
"expo-linking": "~5.0.2",
"expo-splash-screen": "~0.20.5",
"expo-status-bar": "~1.6.0",
"jotai": "^2.0.3",
"lottie-react-native": "^6.4.0",
"react": "18.2.0",
"react-native": "0.72.6",
"react-native-mmkv": "^2.6.3",
"react-native-nfc-manager": "^3.14.8",
"react-native-nfc-manager": "^3.14.12",
"react-native-safe-area-context": "4.6.3",
"react-native-screens": "~3.22.0",
"url-join": "^5.0.0",
Expand Down
50 changes: 29 additions & 21 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
import {NavigationContainer} from '@react-navigation/native'
import {createNativeStackNavigator} from '@react-navigation/native-stack'
import * as Linking from 'expo-linking'
import {useAtomValue} from 'jotai'
import {useEffect, useState} from 'react'
import {Platform, Text} from 'react-native'
import NfcManager from 'react-native-nfc-manager'
import {Callout} from './components/Callout'
import {NfcModal} from './components/ScanNfcModal'
import Home from './screens/HomeScreen'
import Issue from './screens/IssueScreen'
import Login from './screens/LoginScreen'
import LoteDetail from './screens/LoteDetailScreen'
import ScannedLote from './screens/ScannedLoteScreen'
import Welcome from './screens/WelcomeScreen'
import {lnbitsUrlAtom, nfcModalMessageAtom} from './state/atoms'
import {Platform} from 'react-native'
import {NfcModal} from './components/ScanNfcModal'
import {useEffect, useState} from 'react'
import NfcManager from 'react-native-nfc-manager'
import {Callout} from './components/Callout'
import {nfcModalMessageAtom} from './state/atoms'

const prefix = Linking.createURL('/')
const Stack = createNativeStackNavigator()

export default function App(): JSX.Element {
const lnbitsUrl = useAtomValue(lnbitsUrlAtom)
const [hasNfc, setHasNfc] = useState(false)
const modalMessage = useAtomValue(nfcModalMessageAtom)

const linking = {
prefixes: [prefix],
config: {
screens: {
ScannedLote: ':id',
},
},
}

const checkNfcAvailability = async (): Promise<void> => {
const supported = await NfcManager.isSupported()
setHasNfc(supported)
Expand All @@ -30,21 +40,19 @@ export default function App(): JSX.Element {
}, [])

return (
<NavigationContainer>
<NavigationContainer linking={linking} fallback={<Text>Loading...</Text>}>
<Stack.Navigator initialRouteName="Welcome">
{!lnbitsUrl ? (
<Stack.Screen
name="Welcome"
component={Welcome}
options={{headerShown: false}}
/>
) : (
<Stack.Screen
name="Home"
component={Home}
options={{headerShown: false}}
/>
)}
<Stack.Screen
name="Welcome"
component={Welcome}
options={{headerShown: false}}
/>

<Stack.Screen
name="Home"
component={Home}
options={{headerShown: false}}
/>

<Stack.Screen name="Settings" component={Login} />
<Stack.Screen
Expand Down
18 changes: 8 additions & 10 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,16 +176,14 @@ export function useApiCalls(): Api {

setIsFetching(true)
setLastFetched('scanLnurl')
const result: Response = await fetch(
urlJoin(domain, '/api/v1/lnurlscan/', lnurl),
{
method: 'GET',
headers: {
'Content-type': 'application/json',
'X-Api-Key': apiKey,
},
}
)
const url = urlJoin(domain, '/api/v1/lnurlscan/', lnurl)
const result: Response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': apiKey,
},
})

if (!result.ok) {
setIsFetching(false)
Expand Down
8 changes: 1 addition & 7 deletions src/screens/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ function Home({navigation}: {navigation: any}): JSX.Element {
void (async () => {
try {
const lnurlFromNfc = await readNfc()
if (!lnurlFromNfc) return
const scanResultJson = await scanLnurl(lnurlFromNfc)
const temporaryAmount = scanResultJson.maxWithdrawable / 1000
const createdInvoice = await getInvoice(temporaryAmount)
Expand All @@ -79,10 +80,6 @@ function Home({navigation}: {navigation: any}): JSX.Element {
})()
}

// const handleScannedLotePress = (): void => {
// navigation.navigate('ScannedLote', {record: recordx})
// }

return (
<View style={styles.container}>
<Feather
Expand Down Expand Up @@ -125,9 +122,6 @@ function Home({navigation}: {navigation: any}): JSX.Element {
</View>
<Text>{'\n'} </Text>
{returnAvailableBalance()}
{/* <TouchableOpacity onPress={handleScannedLotePress} disabled={isFetching}>
<Text style={styles.link}>💡 Open Scanned Lote</Text>
</TouchableOpacity> */}
</View>
)
}
Expand Down
35 changes: 7 additions & 28 deletions src/screens/ScannedLoteScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,17 @@
import {useAtom} from 'jotai'
import React from 'react'
import {Text, TouchableOpacity, View} from 'react-native'
import {isFetchingAtom} from '../state/atoms'
// import {useApiCalls} from '../api'
import {Text, View} from 'react-native'

import {styles} from '../theme'
// import {writeNdef} from '../utils/nfc'

function ScannedLote({route, navigation}: any): JSX.Element {
const [isFetching, setIsFetching] = useAtom(isFetchingAtom)

setIsFetching(false) // TODO: Remove this line
const {record} = route.params

const handleClaimButtonPress = (): void => {
// TODO: Implement this:
// void (async () => {
// try {
// const lnurlFromNfc = await readNfc()
// const scanResultJson = await scanLnurl(lnurlFromNfc)
// const temporaryAmount = scanResultJson.maxWithdrawable / 1000
// const createdInvoice = await getInvoice(temporaryAmount)
// await requestPayment(scanResultJson.callback, createdInvoice)
// setRefreshCounter(refreshCounter + 1)
// } catch (error) {
// console.error(error)
// }
// })()
}
const {id} = route.params

const isUsed = record.uses - record.used <= 0
// const isUsed = record.uses - record.used <= 0

return (
<View style={styles.container}>
<Text style={styles.sectionHeader}>
<Text style={styles.header}>Lote ID: {id}</Text>
{/* <Text style={styles.sectionHeader}>
{isUsed ? 'Lote contains promise of:' : 'Lote was already used'}
</Text>
<Text style={styles.header}>{record.max_withdrawable}</Text>
Expand All @@ -47,7 +26,7 @@ function ScannedLote({route, navigation}: any): JSX.Element {
</TouchableOpacity>
</View>

<Text style={styles.secondaryText}>{record.lnurl}</Text>
<Text style={styles.secondaryText}>{record.lnurl}</Text> */}
</View>
)
}
Expand Down
10 changes: 8 additions & 2 deletions src/screens/WelcomeScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, {useEffect} from 'react'
import {Text, TouchableOpacity, View} from 'react-native'
import {styles} from '../theme'

Expand All @@ -8,10 +8,16 @@ import {isFetchingAtom, lastFetchedAtom, lnbitsUrlAtom} from '../state/atoms'
import constructLnbitsUrl from '../utils/constructLnbitsUrl'

function Welcome({navigation}: {navigation: any}): JSX.Element {
const setLnbitsUrl = useSetAtom(lnbitsUrlAtom)
const [lnbitsUrl, setLnbitsUrl] = useAtom(lnbitsUrlAtom)
const [isFetching, setIsFetching] = useAtom(isFetchingAtom)
const setLastFetched = useSetAtom(lastFetchedAtom)

useEffect(() => {
if (lnbitsUrl) {
navigation.navigate('Home')
}
}, [lnbitsUrl, navigation])

const handleNewUserButton = async (): Promise<void> => {
setIsFetching(true)
setLastFetched('handleNewUserButton')
Expand Down
10 changes: 10 additions & 0 deletions src/utils/isLoteInternal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type {RecordsApi} from '../api'

export default function IsLoteInternal(
lnUrl: string,
records: RecordsApi
): boolean {
const isLoteKnown = records.records.some((record) => record.lnurl === lnUrl)

return isLoteKnown
}
51 changes: 29 additions & 22 deletions src/utils/nfc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ import {Platform} from 'react-native'
import NfcManager, {Ndef, NfcTech} from 'react-native-nfc-manager'
import {nfcModalVisibilityAtom} from '../state/atoms'

export async function readNfc(): Promise<string> {
export async function readNfc(): Promise<string | null> {
getDefaultStore().set(nfcModalVisibilityAtom, true)
let text = ''
let resp: number[] | string | null = null
let uri: string | null = null

try {
const tech = Platform.OS === 'ios' ? NfcTech.MifareIOS : NfcTech.NfcA

resp = await NfcManager.requestTechnology(tech, {
await NfcManager.requestTechnology(tech, {
alertMessage: 'Scan the Lote',
})

Expand All @@ -20,38 +19,43 @@ export async function readNfc(): Promise<string> {
? NfcManager.sendMifareCommandIOS
: NfcManager.transceive

resp = await cmd([0x3a, 4, 4])
const payloadLength = parseInt(resp.toString().split(',')[1])
const data = await cmd([0x3a, 4, 4])
const payloadLength = parseInt(data.toString().split(',')[1]) - 1 // Subtract 1 to account for the URI identifier byte
const payloadPages = Math.ceil(payloadLength / 4)
const startPage = 5
const endPage = startPage + payloadPages - 1

resp = await cmd([0x3a, startPage, endPage])
const bytes = resp.toString().split(',')

for (let i = 0; i < bytes.length; i++) {
if (i < 5) {
continue
}
const response = await cmd([0x3a, startPage, endPage])
const bytes = response.toString().split(',')

for (let i = 3; i < bytes.length; i++) {
// Start from 1 to skip the URI identifier byte
if (parseInt(bytes[i]) === 254) {
break
}

text = text + String.fromCharCode(parseInt(bytes[i]))
uri = uri
? uri + String.fromCharCode(parseInt(bytes[i]))
: String.fromCharCode(parseInt(bytes[i]))
}

await NfcManager.cancelTechnologyRequest()
} catch (ex: any) {
console.log(ex.toString())
} finally {
if (resp) {
await NfcManager.cancelTechnologyRequest()
}
await NfcManager.cancelTechnologyRequest()
getDefaultStore().set(nfcModalVisibilityAtom, false)
}

return text
if (uri) {
console.log('Detected URI:', uri)
const text = uri.replace(/lotes:\/\//g, '')
console.log('Detected text:', text)
return text
} else {
console.log('No URI detected.')
return null
}
}

export const writeNdef = async (
Expand All @@ -63,16 +67,19 @@ export const writeNdef = async (
await NfcManager.requestTechnology(NfcTech.Ndef, {
alertMessage: alert,
})
const prefixedMessage = 'lotes://' + message
const bytes = Ndef.encodeMessage([Ndef.uriRecord(prefixedMessage)])

const bytes = Ndef.encodeMessage([Ndef.textRecord(message)])
await NfcManager.ndefHandler.writeNdefMessage(bytes)

await NfcManager.setAlertMessageIOS('Successfully stored.')
if (Platform.OS === 'ios') {
await NfcManager.setAlertMessageIOS('Successfully stored.')
}
await NfcManager.cancelTechnologyRequest()

return true
} catch (error: any) {
console.log(error)
console.log('WriteNdef error: ', error)
await NfcManager.cancelTechnologyRequest()
return false
} finally {
getDefaultStore().set(nfcModalVisibilityAtom, false)
Expand Down
Loading
Loading