add physical card scan to server page

This commit is contained in:
ppc
2025-01-12 21:14:01 +00:00
parent b2be622271
commit f9b2326853
2 changed files with 57 additions and 36 deletions
+55 -32
View File
@@ -1,27 +1,19 @@
import {BSON} from "realm";
import React, {useCallback, useEffect, useLayoutEffect, useMemo, useState} from "react";
import {ActivityIndicator, Button, Paragraph, Title, useTheme} from "react-native-paper";
import {
Platform,
RefreshControl,
ScrollView,
Share,
StyleSheet,
Text,
TouchableOpacity,
View
} from "react-native";
import {ActivityIndicator, Button, IconButton, Paragraph, Title, useTheme} from "react-native-paper";
import {Platform, RefreshControl, ScrollView, Share, StyleSheet, Text, TouchableOpacity, View} from "react-native";
import {
CircleCheck,
CircleDot,
CreditCard,
Nfc,
Plus,
SmartphoneNfcIcon,
Share as ShareIcon,
WifiOff,
TvMinimalPlay,
Star
Star,
SmartphoneNfc,
SquareArrowOutUpRight
} from "lucide-react-native";
import {EmptyStatePlaceholder} from "../components/EmptyStatePlaceholder";
@@ -33,14 +25,8 @@ import RealmContext from "../models/RealmContext";
import {AMServer} from "../models/AMServer";
import {AMCard} from "../models/AMCard";
import {icCard0008Number} from "../lib/iccard";
import {
AMServerConnectionInfo,
AMServerInstanceMetrics,
fetchServerInfo,
getGameName,
performCardIn
} from "amnet-shared";
import {felicaCardReadAsync, icCard0008Number, ICType} from "../lib/iccard";
import {AMServerConnectionInfo, AMServerInstanceMetrics, fetchServerInfo, getGameName, performCardIn} from "amnet-shared";
import ObjectId = BSON.ObjectId;
@@ -153,7 +139,7 @@ export default function ServerCardIn({route, navigation}) {
const cardId = selectedCard.physicalCardData && server.preferredCardIdFormat === "idm" ? icCard0008Number(selectedCard.physicalCardData.cardIDm) : selectedCard.code;
const abortController = new AbortController();
performCardIn(server, {cardId}, abortController.signal).then(async r => {
performCardIn(server, {cardId}, abortController.signal).then(r => {
const now = new Date();
realm.write(() => {
server.lastUsed = now;
@@ -172,6 +158,30 @@ export default function ServerCardIn({route, navigation}) {
return () => abortController.abort();
}, [server, selectedCard]);
const performPhysicalCardIn = useCallback(async () => {
const card = await felicaCardReadAsync();
if (!card) {
return;
}
setCardInCompleted(false);
const cardId = server.preferredCardIdFormat === "idm" || card.type !== ICType.Amusement ? icCard0008Number(card.idm) : card.accessCode;
const abortController = new AbortController();
performCardIn(server, {cardId}, abortController.signal).then(r => {
setCardInCompleted(r);
}).catch(() => {
setCardInCompleted(false);
}).finally(() => {
setTimeout(() => setCardInCompleted(null), 3000);
});
setTimeout(() => abortController.abort(), 5000);
return () => abortController.abort();
}, [server]);
const performRefresh = () => {
setServerRefreshing(true);
setServerReloadCounter(serverReloadCounter + 1);
@@ -191,7 +201,8 @@ export default function ServerCardIn({route, navigation}) {
}
return (
<ScrollView contentInsetAdjustmentBehavior="automatic" refreshControl={<RefreshControl refreshing={serverRefreshing} onRefresh={performRefresh}/>}>
<ScrollView contentInsetAdjustmentBehavior="automatic"
refreshControl={<RefreshControl refreshing={serverRefreshing} onRefresh={performRefresh}/>}>
<View style={styles.container}>
<StatusAlert server={server} connectionEstablished={serverConnectionSuccess}/>
<View style={styles.section}>
@@ -202,15 +213,27 @@ export default function ServerCardIn({route, navigation}) {
actionIcon={<CircleCheck color="green" size={24}/>}>
{serverDefaultCard?.code == selectedCard.code && <DefaultCardDetail/>}
</ICCard>
<Button textColor="white"
onPress={cardInCallback}
disabled={disableCardIn}
contentStyle={{flexDirection: 'row-reverse'}}
mode={serverConnectionSuccess ? "contained" : "outlined"}
buttonColor={cardInCompleted === false ? "red" : "green"}
icon={() => <Nfc color={disableCardIn ? "gray" : "white"} size={18}/>}>
{cardInCompleted === null ? "Card In" : cardInCompleted ? "Card In Success" : "Failed"}
</Button>
<View style={{display: "flex", flexDirection: "row", gap: 5}}>
<Button textColor="white"
onPress={cardInCallback}
disabled={disableCardIn}
style={{flex: 1}}
contentStyle={{flexDirection: 'row-reverse'}}
mode={serverConnectionSuccess ? "contained" : "outlined"}
buttonColor={cardInCompleted === false ? "red" : "green"}
icon={() => <SquareArrowOutUpRight color={disableCardIn ? "gray" : "white"} size={18}/>}>
{cardInCompleted === null ? "Card In" : cardInCompleted ? "Card In Success" : "Failed"}
</Button>
{Platform.OS === 'ios' && (
<IconButton
mode="outlined"
iconColor="#03a9f4"
disabled={disableCardIn}
onPress={performPhysicalCardIn}
style={{borderRadius: 10, margin: 0, height: "100%", flex: 0.2}}
icon={() => <SmartphoneNfc color={disableCardIn ? "gray" : "white"} size={18}/>}/>
)}
</View>
</>
: <View style={styles.emptyPlaceholderView}>
<EmptyStatePlaceholder message="No card selected" icon={<CreditCard color="gray"/>}/>
+2 -4
View File
@@ -35,7 +35,7 @@ function CardAddMethodSelector() {
manually or generating new ones.</Paragraph>
</View>
<MethodCard color={theme.colors.primary}
<MethodCard color="#03a9f4"
icon={<SmartphoneNfc/>}
title="Scan Physical Card"
description="Amusement IC or Transport cards supported"
@@ -46,13 +46,11 @@ function CardAddMethodSelector() {
<MethodCard color={theme.colors.secondary}
icon={<SquareAsterisk/>}
title="Enter Details Manually"
//description="Manually enter card details (or generate a new one)"
onPress={() => navigation.navigate('Manual')}/>
<MethodCard color="cornflowerblue"
<MethodCard color="#4caf50"
icon={<SquarePlus/>}
title="Generate New Card"
//description="Create a card from scratch"
onPress={() => navigation.navigate('Manual', {autoGenerate: true})}/>
</View>
)