mirror of
https://gitea.tendokyu.moe/ppc/amnet.git
synced 2026-09-27 17:27:55 +03:00
add cardin screen
This commit is contained in:
@@ -1,9 +1,215 @@
|
||||
import {ScrollView} from "react-native";
|
||||
import {Alert, FlatList, RefreshControl, ScrollView, StyleSheet, View} from "react-native";
|
||||
import React, {useCallback, useEffect, useLayoutEffect, useMemo, useState} from "react";
|
||||
import {CircleCheck, CircleDot, CreditCard, Nfc, Plus, SmartphoneNfcIcon, WifiOff} from "lucide-react-native";
|
||||
import {ActivityIndicator, Button, Card, MD3Colors, Paragraph, Title, useTheme} from "react-native-paper";
|
||||
import {EmptyStatePlaceholder} from "../components/EmptyStatePlaceholder";
|
||||
import {AimeCard} from "../components/AimeCard";
|
||||
import {BSON} from "realm";
|
||||
|
||||
import RealmContext from "../models/RealmContext";
|
||||
import {AMServer} from "../models/AMServer";
|
||||
import {AMCard} from "../models/AMCard";
|
||||
import ObjectId = BSON.ObjectId;
|
||||
|
||||
const {useRealm, useQuery} = RealmContext;
|
||||
|
||||
export default function ServerCardIn({route, navigation}) {
|
||||
const realm = useRealm();
|
||||
const theme = useTheme();
|
||||
|
||||
// card listing state
|
||||
const query = useQuery(AMCard);
|
||||
const cards = useMemo(() => query.sorted("lastUsed"), [query]);
|
||||
|
||||
const [selectedCard, setSelectedCard] = useState<AMCard>(null);
|
||||
const [cardInCompleted, setCardInCompleted] = useState<boolean>(null);
|
||||
|
||||
const unselectedCards = useMemo(() => cards.filter(x => !x._id.equals(selectedCard?._id)), [cards, selectedCard]);
|
||||
|
||||
// server state
|
||||
const [serverInfo, setServerInfo] = useState<AMServer>(null);
|
||||
const [serverRefreshing, setServerRefreshing] = useState<boolean>(false);
|
||||
const [serverReloadCounter, setServerReloadCounter] = useState<number>(0);
|
||||
const [connectionTestResult, setConnectionTestResult] = useState<boolean | null>(null);
|
||||
|
||||
const {serverId} = route.params;
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!serverInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigation.setOptions({
|
||||
title: serverInfo.serverName,
|
||||
|
||||
})
|
||||
}, [navigation, serverInfo]);
|
||||
|
||||
// set selected card (once)
|
||||
useEffect(() => {
|
||||
setSelectedCard(cards.length ? cards[0] : null);
|
||||
}, [realm]);
|
||||
|
||||
// refresh server info (server change or reload request)
|
||||
useEffect(() => {
|
||||
const server = realm.objectForPrimaryKey<AMServer>(AMServer, ObjectId.createFromHexString(serverId));
|
||||
|
||||
if (!server) {
|
||||
navigation.goBack();
|
||||
|
||||
Alert.alert("Server not found", "The server you are looking for does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
setServerInfo(server);
|
||||
|
||||
const abortController = new AbortController();
|
||||
server.connect(abortController.signal).then(r => {
|
||||
if (r) {
|
||||
setConnectionTestResult(true);
|
||||
|
||||
realm.write(() => {
|
||||
Object.apply(server, r);
|
||||
server.lastUsed = new Date();
|
||||
});
|
||||
|
||||
// reset server to reflect changes
|
||||
setServerInfo(server);
|
||||
} else {
|
||||
setConnectionTestResult(false);
|
||||
}
|
||||
}).catch(() => {
|
||||
setConnectionTestResult(false);
|
||||
}).finally(() => {
|
||||
setServerRefreshing(false);
|
||||
});
|
||||
|
||||
// pull abort on unmount
|
||||
return () => abortController.abort();
|
||||
}, [serverId, serverReloadCounter, realm]);
|
||||
|
||||
// card in callback
|
||||
const performCardIn = useCallback(() => {
|
||||
setCardInCompleted(false);
|
||||
|
||||
const abortController = new AbortController();
|
||||
serverInfo.submitCard(selectedCard.code, abortController.signal).then(r => {
|
||||
setCardInCompleted(r);
|
||||
realm.write(() => {
|
||||
selectedCard.lastUsed = new Date();
|
||||
setSelectedCard(selectedCard);
|
||||
});
|
||||
}).catch(() => {
|
||||
setCardInCompleted(false);
|
||||
}).finally(() => {
|
||||
setTimeout(() => setCardInCompleted(null), 3000);
|
||||
});
|
||||
|
||||
setTimeout(() => abortController.abort(), 5000);
|
||||
return () => abortController.abort();
|
||||
}, [serverInfo, selectedCard]);
|
||||
|
||||
if (connectionTestResult === null) {
|
||||
return (
|
||||
<View style={{flex: 1, justifyContent: "center", alignItems: "center", gap: 10}}>
|
||||
<ActivityIndicator animating/>
|
||||
<Paragraph>Connecting to server...</Paragraph>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
// cached vars
|
||||
const disableCardIn = cardInCompleted !== null || !connectionTestResult;
|
||||
|
||||
export default function ServerCardIn({navigation}) {
|
||||
return (
|
||||
<ScrollView contentInsetAdjustmentBehavior="always">
|
||||
|
||||
<ScrollView contentInsetAdjustmentBehavior="automatic"
|
||||
contentContainerStyle={{...styles.container, paddingTop: connectionTestResult ? 0 : 10}}
|
||||
refreshControl={<RefreshControl refreshing={serverRefreshing}
|
||||
tintColor={theme.colors.onSurface}
|
||||
onRefresh={() => {
|
||||
setServerRefreshing(true);
|
||||
setServerReloadCounter(serverReloadCounter + 1);
|
||||
}}/>}>
|
||||
{!connectionTestResult && (
|
||||
<Card mode="outlined" style={{borderColor: theme.colors.secondary}}>
|
||||
<Card.Content>
|
||||
<View style={{alignItems: "center", flexDirection: "row", gap: 10}}>
|
||||
<WifiOff color={theme.colors.secondary} size={24}/>
|
||||
<Title style={{color: theme.colors.secondary}}>Connection Failed</Title>
|
||||
</View>
|
||||
<Paragraph>
|
||||
There was an issue connecting to the AimeNet server at <Paragraph
|
||||
style={{fontFamily: "JetBrainsMono_400Regular"}}>{serverInfo.serverAddress}</Paragraph>.
|
||||
Check connections and game status and try again.
|
||||
</Paragraph>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<View style={styles.section}>
|
||||
<Title style={{fontWeight: "bold"}}>Selected Card</Title>
|
||||
{selectedCard ? <>
|
||||
<AimeCard actionDisabled
|
||||
card={selectedCard}
|
||||
actionIcon={<CircleCheck color="green" size={24}/>}/>
|
||||
<Button textColor="white"
|
||||
onPress={performCardIn}
|
||||
disabled={disableCardIn}
|
||||
contentStyle={{flexDirection: 'row-reverse'}}
|
||||
mode={connectionTestResult ? "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={styles.emptyPlaceholderView}>
|
||||
<EmptyStatePlaceholder message="No card selected" icon={<CreditCard color="gray"/>}/>
|
||||
<Button mode="text"
|
||||
icon={() => <Plus color={theme.colors.primary} size={18}/>}
|
||||
onPress={() => navigation.navigate('add-card')}>
|
||||
Add card
|
||||
</Button>
|
||||
</View>}
|
||||
</View>
|
||||
|
||||
{cards.length && (
|
||||
<View style={styles.section}>
|
||||
<Title style={{fontWeight: "bold"}}>Additional Cards</Title>
|
||||
<Button mode="contained"
|
||||
textColor="white"
|
||||
buttonColor={theme.colors.tertiary}
|
||||
icon={() => <Plus color="white" size={18}/>}
|
||||
contentStyle={{flexDirection: 'row-reverse'}}
|
||||
onPress={() => navigation.navigate('add-card')}>
|
||||
Add card
|
||||
</Button>
|
||||
|
||||
{unselectedCards.length
|
||||
? unselectedCards.map(card => (
|
||||
<AimeCard card={card}
|
||||
key={card._id.toHexString()}
|
||||
actionIcon={<CircleDot color={theme.colors.tertiary} size={24}/>}
|
||||
onPress={() => setSelectedCard(card)}/>))
|
||||
: <View style={styles.emptyPlaceholderView}>
|
||||
<EmptyStatePlaceholder message="No additional cards"
|
||||
icon={<SmartphoneNfcIcon color="gray"/>}/>
|
||||
</View>}
|
||||
</View>)}
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
section: {
|
||||
gap: 10
|
||||
},
|
||||
emptyPlaceholderView: {
|
||||
marginVertical: 30,
|
||||
gap: 15
|
||||
},
|
||||
container: {
|
||||
paddingHorizontal: 16,
|
||||
paddingBottom: 25,
|
||||
gap: 20
|
||||
}
|
||||
})
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function ServerManagerStack({navigation}) {
|
||||
</TouchableOpacity>
|
||||
}}/>
|
||||
|
||||
<Stack.Screen name="server-card-in" options={{title: "Server"}} component={ServerCardIn}/>
|
||||
<Stack.Screen name="server-card-in" options={{title: "Server", headerTransparent: true}} component={ServerCardIn}/>
|
||||
</Stack.Group>
|
||||
|
||||
<Stack.Group screenOptions={{presentation: "modal"}}>
|
||||
|
||||
Reference in New Issue
Block a user