import {BSON} from "realm"; import {useSafeAreaInsets} from "react-native-safe-area-context"; import React, {useCallback, useEffect, useLayoutEffect, useMemo, useState} from "react"; import {ActivityIndicator, Button, Paragraph, Title, useTheme} from "react-native-paper"; import {Alert, RefreshControl, ScrollView, Share, StyleSheet, TouchableOpacity, View} from "react-native"; import { CircleCheck, CircleDot, CreditCard, Nfc, Plus, SmartphoneNfcIcon, Share as ShareIcon, WifiOff, TvMinimalPlay } from "lucide-react-native"; import {EmptyStatePlaceholder} from "../components/EmptyStatePlaceholder"; import {AMUIStatusAlert} from "../components/AMUIStatusAlert"; import {AimeCard} from "../components/AimeCard"; import RealmContext from "../models/RealmContext"; import {AMServer} from "../models/AMServer"; import {AMCard} from "../models/AMCard"; import ObjectId = BSON.ObjectId; import { AMServerConnectionInfo, AMServerInstanceMetrics, fetchServerInfo, getGameName, performCardIn } from "amnet-shared"; const {useRealm, useQuery, useObject} = RealmContext; export default function ServerCardIn({route, navigation}) { const realm = useRealm(); const theme = useTheme(); const safeArea = useSafeAreaInsets(); // card listing state const query = useQuery(AMCard); const cards = useMemo(() => query.sorted("lastUsed", true), [query]); const [selectedCard, setSelectedCard] = useState(null); const [cardInCompleted, setCardInCompleted] = useState(null); const unselectedCards = useMemo(() => { // protect against selected card being removed switch (selectedCard?.isValid()) { case true: return cards.filter(x => !x._id.equals(selectedCard._id)); // clear out invalid case false: setSelectedCard(null); return cards; default: return cards; } }, [cards, selectedCard]); // server state const [serverRefreshing, setServerRefreshing] = useState(false); const [serverReloadCounter, setServerReloadCounter] = useState(0); const [connectionTestResult, setConnectionTestResult] = useState(null); let {serverId} = route.params; const server = useObject(AMServer, ObjectId.createFromHexString(serverId)); useLayoutEffect(() => { if (server?.isValid() !== true) { return; } navigation.setOptions({ title: server.serverName, headerRight: () => ( Share.share({url: server.shareUrl})}> ) }); }, [navigation, server]); // card selection, server connection checks useEffect(() => { if (server?.isValid() !== true) { navigation.goBack(); // don't show error on invalid server, just means it was deleted if (!server) { Alert.alert("Server not found", "The server you are looking for does not exist."); } return; } if (selectedCard?.isValid() !== true) { setSelectedCard(cards.length ? cards[0] : null); } const abortController = new AbortController(); fetchServerInfo(server.serverAddress, abortController.signal).then(r => { if (r) { setConnectionTestResult(true); realm.write(() => { server.lastUsed = new Date(); Object.keys(r).forEach(x => server[x] = r[x]); }); } else { setConnectionTestResult(false); } }).catch(() => { setConnectionTestResult(false); }).finally(() => { setServerRefreshing(false); }); setTimeout(() => abortController.abort(), 5000); return () => abortController.abort(); }, [serverId, serverReloadCounter, realm]); // card in callback const cardInCallback = useCallback(() => { setCardInCompleted(false); const abortController = new AbortController(); performCardIn(server, {cardId: selectedCard.code}, abortController.signal).then(async r => { const now = new Date(); setCardInCompleted(r); realm.write(() => { server.lastUsed = now; selectedCard.lastUsed = now; }); setSelectedCard(selectedCard); }).catch(() => { setCardInCompleted(false); }).finally(() => { setTimeout(() => setCardInCompleted(null), 3000); }); setTimeout(() => abortController.abort(), 5000); return () => abortController.abort(); }, [server, selectedCard]); const performRefresh = () => { setServerRefreshing(true); setServerReloadCounter(serverReloadCounter + 1); } const gameName = useMemo(() => getGameName(server.gameId), [server.gameId]); const disableCardIn = useMemo(() => cardInCompleted !== null || !connectionTestResult, [cardInCompleted, connectionTestResult]); if (connectionTestResult === null) { return ( Connecting to server... ) } return ( }> {gameName ? `Play ${gameName}` : "Selected Card"} {selectedCard ? <> }/> : }/> } {cards.length > 0 && ( Additional Cards {unselectedCards.length ? unselectedCards.map((card: AMCard) => ( } onPress={() => setSelectedCard(card)}/>)) : }/> } )} ) } function StatusAlert(props: { server: AMServerConnectionInfo & Partial, connectionEstablished: boolean }) { if (!props.connectionEstablished) { return ( There was an issue connecting to {props.server.serverAddress}. Check the network connection and try again. ) } // don't show if undefined (some older server versions) if (props.server.timeSinceLastPoll === null) { return ( Scanning a card below may not have any effect until the service has fully started up. ) } return null; } const styles = StyleSheet.create({ section: { gap: 10 }, sectionTitle: { fontWeight: "bold" }, emptyPlaceholderView: { marginVertical: 30, gap: 15 }, container: { paddingHorizontal: 16, paddingBottom: 80, gap: 20 } });