mirror of
https://gitea.tendokyu.moe/ppc/amnet.git
synced 2026-10-02 03:37:58 +03:00
84 lines
3.6 KiB
TypeScript
84 lines
3.6 KiB
TypeScript
import {useNavigation} from "@react-navigation/native";
|
|
import {Card, Paragraph, useTheme} from "react-native-paper";
|
|
import {useCallback, useLayoutEffect, useState} from "react";
|
|
import {TextInput, TouchableOpacity, View} from "react-native";
|
|
|
|
import {useModalStyles} from "../../utils/ModalStyles";
|
|
|
|
import RealmContext from "../../models/RealmContext";
|
|
import {AMCard, AMPhysicalCardData} from "../../models/AMCard";
|
|
import {felicaCardReadAsync, icCard0008Number, ICType} from "../../lib/iccard";
|
|
|
|
const {useRealm} = RealmContext;
|
|
|
|
export default function PhysicalCardScan() {
|
|
const realm = useRealm();
|
|
const theme = useTheme();
|
|
const navigation = useNavigation();
|
|
const modalStyles = useModalStyles();
|
|
|
|
const [cardName, setCardName] = useState<string>('');
|
|
|
|
const handleScan = useCallback(async () => {
|
|
const cardInfo = await felicaCardReadAsync();
|
|
|
|
if (!cardInfo) {
|
|
return;
|
|
}
|
|
|
|
realm.write(() => {
|
|
// check if the card has already been registered (convert a manually entered card to a physically scanned card)
|
|
const accessCode = cardInfo.type === ICType.Amusement ? cardInfo.accessCode : icCard0008Number(cardInfo.idm);
|
|
const cardEntity = realm.objects<AMCard>(AMCard.schema.name).find(x => x.code === accessCode) ?? realm.create<AMCard>(AMCard.schema.name, AMCard.generate(accessCode, cardName));
|
|
|
|
// set physical card props
|
|
cardEntity.name = cardName;
|
|
cardEntity.added = new Date();
|
|
cardEntity.physicalCardData ??= realm.objectForPrimaryKey<AMPhysicalCardData>(AMPhysicalCardData.schema.name, cardInfo.idm) ?? realm.create<AMPhysicalCardData>(AMPhysicalCardData.schema.name, {
|
|
cardIDm: cardInfo.idm,
|
|
cardType: cardInfo.type,
|
|
cardVendor: cardInfo.type === ICType.Amusement ? cardInfo.vendor : null
|
|
});
|
|
});
|
|
|
|
// dismiss the parent screen (this screen can only be accessed from modal -> stack)
|
|
navigation.getParent()?.goBack();
|
|
}, [realm, navigation, cardName]);
|
|
|
|
useLayoutEffect(() => {
|
|
const canSave = cardName.length;
|
|
const themeOptions = canSave ? modalStyles.headerText : {color: theme.colors.outline};
|
|
|
|
navigation.setOptions({
|
|
headerRight: () => <TouchableOpacity disabled={!canSave} onPress={() => handleScan()}>
|
|
<Paragraph style={{...themeOptions, fontWeight: "600"}}>Scan</Paragraph>
|
|
</TouchableOpacity>
|
|
})
|
|
}, [navigation, modalStyles, cardName]);
|
|
|
|
return (
|
|
<View style={modalStyles.rootLayout}>
|
|
<Card mode="contained">
|
|
<Card.Content>
|
|
<View style={modalStyles.formItemRow}>
|
|
<Paragraph style={modalStyles.formItemName}>
|
|
Card Name
|
|
</Paragraph>
|
|
<TextInput style={modalStyles.formItemInput}
|
|
autoFocus
|
|
numberOfLines={1}
|
|
value={cardName}
|
|
onChangeText={setCardName}
|
|
placeholder="Primary Card"
|
|
placeholderTextColor={theme.colors.outline}/>
|
|
</View>
|
|
</Card.Content>
|
|
</Card>
|
|
|
|
<Paragraph style={modalStyles.formFootnote}>
|
|
Ensure you have either an Amusement IC or Transport card ready to scan.
|
|
After the scan has completed, the card will be saved to your device.
|
|
</Paragraph>
|
|
</View>
|
|
)
|
|
} |