2020-09-02 21:09:49 +00:00
// @refresh reset
import AsyncStorage from '@react-native-community/async-storage' ;
import { format , parse } from 'date-fns' ;
2020-08-29 03:40:55 +00:00
import { BarCodeEvent , BarCodeScanner , PermissionResponse } from 'expo-barcode-scanner' ;
2020-09-02 21:09:49 +00:00
// @ts-ignore
import * as firebase from 'firebase' ;
import 'firebase/firestore' ;
import React , { ReactElement , useCallback , useEffect , useState } from 'react' ;
2020-09-06 03:16:46 +00:00
import { AppRegistry , SafeAreaView , View , YellowBox } from 'react-native' ;
import { Appbar , DefaultTheme , Provider as PaperProvider , Snackbar , Title , } from 'react-native-paper' ;
2020-09-01 02:12:32 +00:00
import { expo as appExpo } from './app.json' ;
2020-08-29 03:40:55 +00:00
import { CancelButton } from './components/Common' ;
2020-09-06 03:16:46 +00:00
import { InputLineCountButton , InputLineCountScreen } from './components/LineCount' ;
2020-08-29 03:40:55 +00:00
import { BarCodeDisplay , PrintButton , PrintingMessage } from './components/Print' ;
2020-09-02 16:13:16 +00:00
import { IdNumberInput , InputIdButton , ScanButton , Scanner } from './components/Scan' ;
2020-09-06 03:16:46 +00:00
import { SettingsScreen } from './components/Settings' ;
2020-09-01 02:12:32 +00:00
import { colors , styles } from './components/Styles' ;
2020-08-29 03:40:55 +00:00
import { BarcodeScannerAppState } from './models/BarcodeScannerAppState' ;
2020-09-04 21:50:50 +00:00
import { CameraType , ElementProps , StateProps } from './models/ElementProps' ;
2020-09-06 03:16:46 +00:00
import { LineCount } from './models/LineCount' ;
2020-09-02 21:09:49 +00:00
import { Sample } from './models/Sample' ;
const firebaseConfig = {
apiKey : 'api_key_goes_here' ,
authDomain : 'auth_domain_goes_here' ,
databaseURL : 'database_url_goes_here' ,
projectId : 'project_id_goes_here' ,
storageBucket : 'storage_bucket_goes_here' ,
messagingSenderId : 'sender_id_goes_here' ,
appId : 'app_id_goes_here'
} ;
// Initialize Firebase if not already initialized.
if ( firebase . apps . length === 0 ) {
firebase . initializeApp ( firebaseConfig ) ;
}
YellowBox . ignoreWarnings ( [
'Setting a timer for a long period of time' , // Ignore Firebase timer warnings
2020-09-04 21:50:50 +00:00
'Remote debugger is in a background tab' , // Ignore Firebase timer warnings
2020-09-02 21:09:49 +00:00
] ) ;
const db = firebase . firestore ( ) ;
const samplesCollection = db . collection ( 'samples' ) ;
2020-09-06 03:16:46 +00:00
const countsCollection = db . collection ( 'counts' ) ;
const dateFormat = 'yyyyMMddHHmm' ;
2020-08-29 03:40:55 +00:00
2020-09-01 02:12:32 +00:00
const theme = {
. . . DefaultTheme ,
colors : colors ,
}
2020-09-01 19:55:23 +00:00
export default function Main() {
2020-08-29 03:40:55 +00:00
const [ appState , setAppState ] = useState < BarcodeScannerAppState > ( BarcodeScannerAppState . INITIAL ) ;
2020-09-02 21:09:49 +00:00
const [ sampleId , setSampleId ] = useState < string > ( '' ) ;
const [ barCodeId , setBarCodeId ] = useState < string > ( '' ) ;
const [ sampleDate , setSampleDate ] = useState < Date > ( new Date ( ) ) ;
2020-08-29 03:40:55 +00:00
const [ locationStr , setLocationStr ] = useState < string > ( '4321' ) ;
2020-09-02 16:13:16 +00:00
const [ errorMessage , setErrorMessage ] = useState < string > ( '' ) ;
2020-09-02 21:09:49 +00:00
const [ samples , setSamples ] = useState < Sample [ ] > ( [ ] ) ;
2020-09-06 03:16:46 +00:00
const [ lineCounts , setLineCounts ] = useState < LineCount [ ] > ( [ ] ) ;
2020-09-04 21:50:50 +00:00
const [ cameraType , setCameraType ] = useState < CameraType > ( 'back' ) ;
2020-08-29 03:40:55 +00:00
useEffect ( ( ) = > {
BarCodeScanner . requestPermissionsAsync ( ) . then ( ( value : PermissionResponse ) = > {
if ( value . granted ) {
setAppState ( BarcodeScannerAppState . DEFAULT ) ;
} else {
setAppState ( BarcodeScannerAppState . ERROR ) ;
}
} ) ;
2020-09-02 21:09:49 +00:00
2020-09-06 03:16:46 +00:00
const unsubscribeSamples = samplesCollection . onSnapshot ( querySnapshot = > {
2020-09-02 21:09:49 +00:00
// Transform and sort the data returned from Firebase
const samplesFirestore = querySnapshot
. docChanges ( )
. filter ( ( { type } ) = > type === 'added' )
. map ( ( { doc } ) = > {
const sample = doc . data ( ) ;
return { . . . sample , createdAt : sample.createdAt.toDate ( ) } as Sample ;
} )
. sort ( ( a , b ) = > b . createdAt . getTime ( ) - a . createdAt . getTime ( ) ) ;
appendSamples ( samplesFirestore ) ;
} ) ;
2020-09-06 03:16:46 +00:00
const unsubscribeCounts = countsCollection . onSnapshot ( querySnapshot = > {
// Transform and sort the data returned from Firebase
const lineCountsFirestore = querySnapshot
. docChanges ( )
. filter ( ( { type } ) = > type === 'added' )
. map ( ( { doc } ) = > {
const lineCount = doc . data ( ) ;
return { . . . lineCount , createdAt : lineCount.createdAt.toDate ( ) } as LineCount ;
} )
. sort ( ( a , b ) = > b . createdAt . getTime ( ) - a . createdAt . getTime ( ) ) ;
appendLineCounts ( lineCountsFirestore ) ;
} ) ;
2020-09-09 12:39:31 +00:00
return ( ) = > {
unsubscribeSamples ( )
unsubscribeCounts ( )
}
2020-08-29 03:40:55 +00:00
} , [ ] ) ;
2020-09-02 21:09:49 +00:00
const _doNothing = ( ) = > {
} ;
2020-09-02 16:13:16 +00:00
const _scan = ( ) = > {
setErrorMessage ( '' ) ;
setAppState ( BarcodeScannerAppState . SCANNING ) ;
} ;
const _inputIdNumber = ( ) = > {
setErrorMessage ( '' ) ;
setAppState ( BarcodeScannerAppState . INPUT ) ;
} ;
2020-09-06 03:16:46 +00:00
const _inputLineCount = ( ) = > {
setErrorMessage ( '' ) ;
setAppState ( BarcodeScannerAppState . COUNT ) ;
} ;
2020-09-01 02:12:32 +00:00
const _print = ( ) = > setAppState ( BarcodeScannerAppState . PRINTING ) ;
2020-09-02 21:09:49 +00:00
const _printed = ( ) = > setAppState ( BarcodeScannerAppState . PRINTED ) ;
2020-09-01 02:12:32 +00:00
const _home = ( ) = > setAppState ( BarcodeScannerAppState . DEFAULT ) ;
2020-09-01 19:55:23 +00:00
const _settings = ( ) = > setAppState ( BarcodeScannerAppState . SETTINGS ) ;
2020-09-01 02:12:32 +00:00
2020-08-29 03:40:55 +00:00
const handleBarCodeScanned = ( e : BarCodeEvent ) = > {
2020-09-01 19:55:23 +00:00
// Make sure the data is the right length.
2020-09-02 16:13:16 +00:00
// Scanned barcodes will be exactly 14 digits long.
// Manually-entered ID numbers will be exactly 9 digits long.
2020-09-01 19:55:23 +00:00
const barCodeString = e . data ;
2020-09-02 16:13:16 +00:00
const pattern = /^[\d]{14}$|^[\d]{9}$/ ;
2020-09-04 21:50:50 +00:00
console . log ( 'barCodeString' , barCodeString ) ;
2020-09-01 19:55:23 +00:00
if ( pattern . test ( barCodeString ) ) {
2020-09-02 16:13:16 +00:00
const cardId = e . data . slice ( 0 , 9 ) ;
2020-09-03 22:27:44 +00:00
const newSampleDate = new Date ( ) ;
2020-09-06 03:16:46 +00:00
const newSampleId = [ cardId , format ( newSampleDate , dateFormat ) , locationStr ] . join ( '-' ) ;
2020-09-03 22:27:44 +00:00
setSampleId ( newSampleId ) ;
2020-09-02 21:09:49 +00:00
setBarCodeId ( cardId ) ;
2020-09-03 22:27:44 +00:00
setSampleDate ( newSampleDate ) ;
2020-09-01 19:55:23 +00:00
setAppState ( BarcodeScannerAppState . SCANNED ) ;
} else {
2020-09-02 16:13:16 +00:00
setErrorMessage ( ` The barcode data " ${ e . data } " is not from a valid ID card. ` ) ;
2020-09-01 19:55:23 +00:00
setAppState ( BarcodeScannerAppState . ERROR ) ;
}
2020-08-29 03:40:55 +00:00
} ;
2020-09-06 03:16:46 +00:00
const handleLineCountSubmitted = ( newCount : number ) = > {
const now = new Date ( ) ;
const newId = ` ${ locationStr } - ${ format ( now , dateFormat ) } ` ;
const newData : LineCount = {
id : newId ,
lineCount : newCount ,
locationId : locationStr ,
createdAt : now ,
} ;
sendDataToFirebase ( [ newData ] , countsCollection ) ;
}
2020-09-02 21:09:49 +00:00
const appendSamples = useCallback ( ( newSamples ) = > {
setSamples ( ( previousSamples ) = > previousSamples . concat ( newSamples ) ) ;
} , [ samples ] ) ;
2020-09-06 03:16:46 +00:00
const appendLineCounts = useCallback ( ( newLineCounts ) = > {
setLineCounts ( ( previousLineCounts ) = > previousLineCounts . concat ( newLineCounts ) ) ;
} , [ lineCounts ] ) ;
const sendDataToFirebase = async ( newData : Array < Sample | LineCount > , collection : firebase.firestore.CollectionReference ) = > {
const writes = newData . map ( ( s : Sample | LineCount ) = > collection . doc ( s . id ) . set ( s ) ) ;
2020-09-02 21:09:49 +00:00
await Promise . all ( writes ) ;
}
2020-09-06 03:16:46 +00:00
const ErrorMessage = ( props : ElementProps ) : ReactElement = > {
2020-09-01 19:55:23 +00:00
return < View style = { styles . fullScreen } >
< View style = { styles . container } >
< ScanButton onClicked = { _scan } / >
2020-09-02 16:13:16 +00:00
< InputIdButton onClicked = { _inputIdNumber } / >
2020-09-01 19:55:23 +00:00
< / View >
< Snackbar
visible = { appState === BarcodeScannerAppState . ERROR }
onDismiss = { _doNothing }
style = { styles . error }
2020-09-02 16:13:16 +00:00
> { errorMessage === '' ? 'Something went wrong. Try again.' : errorMessage } < / Snackbar >
2020-09-01 19:55:23 +00:00
< / View >
2020-08-29 03:40:55 +00:00
}
2020-09-06 03:16:46 +00:00
const LoadingMessage = ( props : ElementProps ) : ReactElement = > {
2020-09-01 19:55:23 +00:00
return < Snackbar
visible = { appState === BarcodeScannerAppState . INITIAL }
onDismiss = { _doNothing }
> Loading . . . < / Snackbar > ;
2020-08-29 03:40:55 +00:00
}
2020-09-06 03:16:46 +00:00
const SuccessMessage = ( props : ElementProps ) : ReactElement = > {
2020-09-01 19:55:23 +00:00
return < Title > Your barcode label has printed successfully . < / Title > ;
2020-08-29 03:40:55 +00:00
}
2020-09-06 03:16:46 +00:00
const ActionButtons = ( props : ElementProps ) : ReactElement = > {
2020-09-01 19:55:23 +00:00
return < View >
2020-09-01 02:12:32 +00:00
< PrintButton onClicked = { _print } / >
< CancelButton onClicked = { _home } / >
< / View >
}
2020-09-01 19:55:23 +00:00
function App ( props : StateProps ) : ReactElement {
2020-08-29 03:40:55 +00:00
switch ( props . appState ) {
case BarcodeScannerAppState . INITIAL :
return < LoadingMessage / > ;
case BarcodeScannerAppState . DEFAULT :
2020-09-01 19:55:23 +00:00
return < View style = { styles . container } >
< ScanButton onClicked = { _scan } / >
2020-09-02 16:13:16 +00:00
< InputIdButton onClicked = { _inputIdNumber } / >
2020-09-06 03:16:46 +00:00
< InputLineCountButton onClicked = { _inputLineCount } / >
2020-09-01 19:55:23 +00:00
< / View > ;
2020-08-29 03:40:55 +00:00
case BarcodeScannerAppState . PRINTED :
2020-09-09 12:39:31 +00:00
// TODO: Detect when user is online. If online, sync data with Firebase. If not online, just go home. Alternatively, set up a timer that periodically syncs data with the database.
2020-09-02 21:09:49 +00:00
// Upload any changes to Firebase
AsyncStorage . getAllKeys ( ) . then ( keys = > {
const newSamples = keys
. filter ( s = > /^[\d]{9}-[\d]{12}-[\d]{4}$/ . test ( s ) )
. map ( s = > {
const propsArray = s . split ( '-' ) ;
return {
id : s ,
barcodeId : propsArray [ 0 ] ,
2020-09-06 03:16:46 +00:00
createdAt : parse ( propsArray [ 1 ] , dateFormat , new Date ( ) ) ,
2020-09-02 21:09:49 +00:00
locationId : propsArray [ 2 ] ,
} as Sample ;
} ) ;
2020-09-09 12:39:31 +00:00
sendDataToFirebase ( newSamples , samplesCollection ) . then ( ( ) = > {
return _home ;
} ) ;
2020-09-02 21:09:49 +00:00
} ) ;
2020-08-29 03:40:55 +00:00
return < SuccessMessage / > ;
case BarcodeScannerAppState . PRINTING :
2020-09-02 21:09:49 +00:00
return < View style = { styles . container } >
< PrintingMessage
onCancel = { _printed }
id = { sampleId }
barCodeId = { barCodeId }
date = { sampleDate }
location = { locationStr }
/ >
< / View > ;
2020-08-29 03:40:55 +00:00
case BarcodeScannerAppState . SCANNED :
2020-09-01 02:12:32 +00:00
return < View style = { styles . container } >
2020-09-02 21:09:49 +00:00
< BarCodeDisplay
id = { sampleId }
barCodeId = { barCodeId }
date = { sampleDate }
location = { locationStr }
/ >
2020-09-01 19:55:23 +00:00
< ActionButtons / >
2020-08-29 03:40:55 +00:00
< / View > ;
case BarcodeScannerAppState . SCANNING :
2020-09-01 19:55:23 +00:00
return < Scanner
onScanned = { handleBarCodeScanned }
onCancel = { _home }
2020-09-04 21:50:50 +00:00
cameraType = { cameraType }
2020-09-01 19:55:23 +00:00
/ > ;
2020-09-02 16:13:16 +00:00
case BarcodeScannerAppState . INPUT :
return < IdNumberInput
onScanned = { handleBarCodeScanned }
onCancel = { _home }
2020-09-04 21:50:50 +00:00
cameraType = { undefined }
2020-09-02 16:13:16 +00:00
/ > ;
2020-09-06 03:16:46 +00:00
case BarcodeScannerAppState . COUNT :
return < InputLineCountScreen
onSave = { handleLineCountSubmitted }
onCancel = { _home }
/ > ;
2020-09-01 19:55:23 +00:00
case BarcodeScannerAppState . SETTINGS :
2020-09-06 03:16:46 +00:00
return < SettingsScreen
cameraType = { cameraType }
locationStr = { locationStr }
onSave = { ( newCameraType : CameraType , newLocationStr : string ) = > {
setCameraType ( newCameraType ) ;
setLocationStr ( newLocationStr ) ;
_home ( ) ;
} }
onCancel = { _home }
/ > ;
2020-08-29 03:40:55 +00:00
default :
return < ErrorMessage / > ;
}
}
return (
2020-09-01 02:12:32 +00:00
< PaperProvider theme = { theme } >
2020-09-01 19:55:23 +00:00
< Appbar.Header dark = { true } >
< Appbar.Content title = { ` ${ appExpo . description } # ${ locationStr } ` } / >
< Appbar.Action icon = "home" onPress = { _home } / >
< Appbar.Action icon = "settings" onPress = { _settings } / >
2020-09-01 02:12:32 +00:00
< / Appbar.Header >
2020-09-01 19:55:23 +00:00
< SafeAreaView style = { styles . safeAreaView } >
2020-09-01 02:12:32 +00:00
< App appState = { appState } / >
< / SafeAreaView >
< / PaperProvider >
2020-08-29 03:40:55 +00:00
) ;
} ;
2020-09-01 02:12:32 +00:00
AppRegistry . registerComponent ( appExpo . name , ( ) = > Main ) ;