diff --git a/amnet-native/app/ServerCardIn.tsx b/amnet-native/app/ServerCardIn.tsx index ddad5b1..ac4ad50 100644 --- a/amnet-native/app/ServerCardIn.tsx +++ b/amnet-native/app/ServerCardIn.tsx @@ -1,15 +1,15 @@ import {BSON} from "realm"; import NfcManager from "react-native-nfc-manager"; import React, {useCallback, useEffect, useLayoutEffect, useMemo, useState} from "react"; +import {Platform, RefreshControl, ScrollView, Share, StyleSheet, Text, 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, + CircleEllipsis, CreditCard, Plus, SmartphoneNfcIcon, - Share as ShareIcon, WifiOff, TvMinimalPlay, Star, @@ -38,6 +38,7 @@ import { import ObjectId = BSON.ObjectId; import {AMCardRecentUse} from "../models/AMCardRecentUse"; +import {MenuAction, MenuView, NativeActionEvent} from "@react-native-menu/menu"; const {useRealm, useQuery, useObject} = RealmContext; @@ -52,8 +53,24 @@ export default function ServerCardIn({route, navigation}) { query: q => q.sorted("lastUsed", true) }); + // header actions + const menuCallback = useCallback((e: NativeActionEvent) => { + switch (e.nativeEvent.event) { + case "share": + Share.share({url: server.shareUrl}); + break; + + case "delete": + realm.write(() => { + realm.delete(server); + navigation.goBack(); + }); + break; + } + }, [realm, server, navigation]); + // nfc support - const [nfcSupported, setNfcSupported] = useState(Platform.OS === 'android' ? false : null); + const [nfcSupported, setNfcSupported] = useState(Platform.select({android: false, default: null})); useEffect(() => { if (nfcSupported !== null) { return; @@ -91,30 +108,20 @@ export default function ServerCardIn({route, navigation}) { navigation.setOptions({ title: server.serverName, headerRight: () => ( - Share.share({url: server.shareUrl})}> - - + + + ) }); }, [navigation, server]); - // invalid card/server selection useEffect(() => { if (server?.isValid() !== true) { navigation.goBack(); return; } - }, [server]); - useEffect(() => { - if (userSelectedCard?.isValid() === false) { - setUserSelectedCard(null); - } - }, [userSelectedCard]); - - // handle auto-refresh - useEffect(() => { - if (!serverConnectionSuccess || server?.isValid() !== true) { + if (!serverConnectionSuccess) { return; } @@ -125,6 +132,13 @@ export default function ServerCardIn({route, navigation}) { } }, [serverConnectionSuccess, server]); + // invalid card/server selection + useEffect(() => { + if (userSelectedCard?.isValid() === false) { + setUserSelectedCard(null); + } + }, [userSelectedCard]); + // server info update useEffect(() => { const abortController = new AbortController(); @@ -239,8 +253,7 @@ export default function ServerCardIn({route, navigation}) { - {server.gameId ? `Play ${getGameName(server.gameId)}` : "Selected Card"} + {server.gameId ? `Play ${getGameName(server.gameId)}` : "Selected Card"} {selectedCard ? <> cardInCallback(selectedCard)} - icon={() => }> + icon={() => }> {cardInCompleted === null ? "Card In" : cardInCompleted ? "Card In Success" : "Failed"} {nfcSupported === true && ( @@ -265,8 +277,7 @@ export default function ServerCardIn({route, navigation}) { disabled={disableCardIn} onPress={performPhysicalCardIn} style={{borderRadius: 10, margin: 0, height: "100%", flex: 0.2}} - icon={() => }/> + icon={() => }/> )} @@ -355,6 +366,28 @@ function DefaultCardDetail() { ) } +const menuActions: MenuAction[] = [ + { + id: "share", + title: "Share", + image: Platform.select({ + ios: "square.and.arrow.up", + android: "share" + }) + }, + { + id: "delete", + title: "Delete Server", + attributes: { + destructive: true, + }, + image: Platform.select({ + ios: "trash", + android: "delete" + }) + } +]; + const styles = StyleSheet.create({ section: { gap: 10 @@ -367,7 +400,7 @@ const styles = StyleSheet.create({ gap: 15 }, container: { - paddingTop: Platform.OS === 'android' ? 120 : 0, + paddingTop: Platform.select({android: 120, default: 0}), paddingHorizontal: 16, paddingBottom: 80, gap: 20 diff --git a/amnet-native/app/ServerManager.tsx b/amnet-native/app/ServerManager.tsx index 1c92a70..a3eafe3 100644 --- a/amnet-native/app/ServerManager.tsx +++ b/amnet-native/app/ServerManager.tsx @@ -1,77 +1,47 @@ +import {getGameName} from "amnet-shared"; +import {FlatList, Platform, View} from "react-native"; import {Button, Paragraph, useTheme} from "react-native-paper"; import React, {useCallback, useLayoutEffect, useMemo, useState} from "react"; import {Globe, History, Joystick, Plus, ServerOff} from "lucide-react-native"; -import {FlatList, NativeSyntheticEvent, Platform, ScrollView, Share, View} from "react-native"; -import ContextMenu, {ContextMenuOnPressNativeEvent} from "react-native-context-menu-view"; -import {EmptyStatePlaceholder} from "../components/EmptyStatePlaceholder"; -import {AMUICard, AMUICardDetail} from "../components/AMUICard"; + import TimeAgo from "../components/TimeAgo"; +import {AMUICard, AMUICardDetail} from "../components/AMUICard"; +import {EmptyStatePlaceholder} from "../components/EmptyStatePlaceholder"; -import RealmContext from "../models/RealmContext"; import {AMServer} from "../models/AMServer"; -import {getGameName} from "amnet-shared"; +import RealmContext from "../models/RealmContext"; -const {useRealm, useQuery} = RealmContext; +const {useQuery} = RealmContext; function ServerCard(props: { server: AMServer, navigation: any }) { - const realm = useRealm(); const gameName = useMemo(() => getGameName(props.server.gameId), [props.server.gameId]); - const pageCallback = useCallback(() => { props.navigation.navigate('server', { serverId: props.server._id.toHexString() }); }, [props.navigation, props.server]); - const pageContextMenuCallback = useCallback((action: NativeSyntheticEvent) => { - switch (action.nativeEvent.indexPath.toString()) { - case "0": - pageCallback(); - return; - - case "1,0": - Share.share({url: props.server.shareUrl}); - return; - - case "1,1": - realm.write(() => realm.delete(props.server)); - return; - } - }, [realm, props.server]); - return ( - - {}}> + + + + + {props.server.serverAddress} + + + {gameName && ( - - - {props.server.serverAddress} - + + {gameName} - {gameName && ( - - - {gameName} - - )} - - - {props.server.lastUsed - ? () - : (never)} - - - + )} + + + {props.server.lastUsed + ? () + : (never)} + + ) } diff --git a/amnet-native/package-lock.json b/amnet-native/package-lock.json index 4fd9c55..8a8a3e1 100644 --- a/amnet-native/package-lock.json +++ b/amnet-native/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@babel/runtime": "^7.27.0", "@expo-google-fonts/jetbrains-mono": "^0.2.3", + "@react-native-menu/menu": "^1.2.3", "@react-navigation/bottom-tabs": "^7.3.3", "@react-navigation/native": "^7.0.19", "@react-navigation/native-stack": "^7.3.3", @@ -3456,6 +3457,16 @@ "node": ">=14" } }, + "node_modules/@react-native-menu/menu": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@react-native-menu/menu/-/menu-1.2.3.tgz", + "integrity": "sha512-sEfiVIivsa0lSelFm9Wbm/RAi+XoEHc75GGhjwvSrj9KSCVvNNXwr9F8l42e1t/lzYvVYzmkYxLG6VKxrDYJiw==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/@react-native/assets-registry": { "version": "0.76.8", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.76.8.tgz", @@ -7252,9 +7263,9 @@ } }, "node_modules/image-size": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.0.tgz", - "integrity": "sha512-4S8fwbO6w3GeCVN6OPtA9I5IGKkcDMPcKndtUlpJuCwu7JLjtj7JZpwqLuyY2nrmQT3AWsCJLSKPsc2mPBSl3w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", + "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", "license": "MIT", "dependencies": { "queue": "6.0.2" diff --git a/amnet-native/package.json b/amnet-native/package.json index da5bea2..dffd7cb 100644 --- a/amnet-native/package.json +++ b/amnet-native/package.json @@ -11,6 +11,7 @@ "dependencies": { "@babel/runtime": "^7.27.0", "@expo-google-fonts/jetbrains-mono": "^0.2.3", + "@react-native-menu/menu": "^1.2.3", "@react-navigation/bottom-tabs": "^7.3.3", "@react-navigation/native": "^7.0.19", "@react-navigation/native-stack": "^7.3.3",