278 lines
No EOL
11 KiB
TypeScript
278 lines
No EOL
11 KiB
TypeScript
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
import { useRouter } from "expo-router";
|
|
import React, { useEffect, useState } from "react";
|
|
import {
|
|
Alert,
|
|
ImageBackground,
|
|
KeyboardAvoidingView,
|
|
Platform,
|
|
ScrollView,
|
|
Text,
|
|
TextInput,
|
|
TouchableOpacity,
|
|
View
|
|
} from "react-native";
|
|
import { SafeAreaView } from "react-native-safe-area-context";
|
|
import { styles } from "./styles";
|
|
|
|
const converterBgImage = require("../assets/images/saved-coordinates.jpg");
|
|
|
|
// Define a type for a single coordinate entry
|
|
interface Coordinate {
|
|
id: string;
|
|
name: string;
|
|
x: string;
|
|
y: string;
|
|
z: string;
|
|
}
|
|
|
|
export default function CoordinateConverter() {
|
|
const router = useRouter();
|
|
|
|
// State for the list of saved coordinates
|
|
const [savedCoordinates, setSavedCoordinates] = useState<Coordinate[]>(
|
|
[]
|
|
);
|
|
// State for new coordinate input fields
|
|
const [name, setName] = useState<string>("");
|
|
const [xCoord, setXCoord] = useState<string>("");
|
|
const [yCoord, setYCoord] = useState<string>("");
|
|
const [zCoord, setZCoord] = useState<string>("");
|
|
|
|
// Key for AsyncStorage
|
|
const STORAGE_KEY = "@saved_minecraft_coordinates";
|
|
|
|
// Persistence Logic
|
|
// Load coordinates from AsyncStorage on component mount
|
|
useEffect(() => {
|
|
loadCoordinates();
|
|
}, []);
|
|
|
|
// Save coordinates to AsyncStorage
|
|
useEffect(() => {
|
|
saveCoordinates();
|
|
}, [savedCoordinates]);
|
|
|
|
const loadCoordinates = async () => {
|
|
try {
|
|
const jsonValue = await AsyncStorage.getItem(STORAGE_KEY);
|
|
if (jsonValue != null) {
|
|
const parsedValue = JSON.parse(jsonValue) as Coordinate[];
|
|
if (Array.isArray(parsedValue)) {
|
|
setSavedCoordinates(parsedValue);
|
|
} else {
|
|
console.warn(
|
|
"Loaded data is not an array, initializing with empty array."
|
|
);
|
|
setSavedCoordinates([]);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error("Failed to load coordinates:", e);
|
|
Alert.alert(
|
|
"Error",
|
|
"Failed to load saved coordinates. Please try again."
|
|
);
|
|
}
|
|
};
|
|
|
|
const saveCoordinates = async () => {
|
|
try {
|
|
const jsonValue = JSON.stringify(savedCoordinates);
|
|
await AsyncStorage.setItem(STORAGE_KEY, jsonValue);
|
|
} catch (e) {
|
|
console.error("Failed to save coordinates:", e);
|
|
Alert.alert(
|
|
"Error",
|
|
"Failed to save coordinates. Please try again."
|
|
);
|
|
}
|
|
};
|
|
|
|
// Coordinate Logic
|
|
const handleAddCoordinate = () => {
|
|
const trimmedName = name.trim();
|
|
const trimmedX = xCoord.trim();
|
|
const trimmedY = yCoord.trim();
|
|
const trimmedZ = zCoord.trim();
|
|
|
|
const integerRegex = /^-?\d+$/;
|
|
|
|
if (
|
|
!integerRegex.test(trimmedX) ||
|
|
!integerRegex.test(trimmedY) ||
|
|
!integerRegex.test(trimmedZ)
|
|
) {
|
|
Alert.alert(
|
|
"Invalid Input",
|
|
"Please enter valid integer numbers for X, Y, and Z (e.g., 100, -50). Decimals or non-numeric characters are not allowed."
|
|
);
|
|
return;
|
|
}
|
|
|
|
const newCoordinate: Coordinate = {
|
|
id: Date.now().toString(),
|
|
name: trimmedName || `Unnamed Location ${savedCoordinates.length + 1}`,
|
|
x: trimmedX,
|
|
y: trimmedY,
|
|
z: trimmedZ
|
|
};
|
|
|
|
setSavedCoordinates((prevCoords) => [...prevCoords, newCoordinate]);
|
|
|
|
// Clear input fields
|
|
setName("");
|
|
setXCoord("");
|
|
setYCoord("");
|
|
setZCoord("");
|
|
};
|
|
|
|
const handleDeleteCoordinate = (id: string) => {
|
|
Alert.alert(
|
|
"Delete Coordinate",
|
|
"Are you sure you want to delete this coordinate?",
|
|
[
|
|
{
|
|
text: "Cancel",
|
|
style: "cancel"
|
|
},
|
|
{
|
|
text: "Delete",
|
|
onPress: () => {
|
|
setSavedCoordinates((prevCoords) =>
|
|
prevCoords.filter((coord) => coord.id !== id)
|
|
);
|
|
},
|
|
style: "destructive"
|
|
}
|
|
],
|
|
{ cancelable: true }
|
|
);
|
|
};
|
|
|
|
return (
|
|
<View style={styles.converterScreenRoot}>
|
|
<ImageBackground
|
|
source={converterBgImage}
|
|
style={styles.converterBackgroundImage}
|
|
resizeMode="cover"
|
|
>
|
|
<View style={styles.converterBackgroundOverlay} />
|
|
</ImageBackground>
|
|
|
|
<SafeAreaView style={styles.converterContentWrapper}>
|
|
<KeyboardAvoidingView
|
|
style={styles.converterContainer}
|
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
>
|
|
<ScrollView
|
|
contentContainerStyle={styles.converterScrollContent}
|
|
>
|
|
{/* Custom Back Button */}
|
|
<TouchableOpacity
|
|
onPress={() => router.back()}
|
|
style={styles.backButton}
|
|
>
|
|
<Text style={styles.backButtonText}>
|
|
← Go Back
|
|
</Text>
|
|
</TouchableOpacity>
|
|
|
|
<Text style={styles.converterTitle}>
|
|
Saved Coordinates
|
|
</Text>
|
|
|
|
{/* Coordinate Input Section */}
|
|
<View style={styles.coordinateInputSection}>
|
|
<TextInput
|
|
style={styles.coordinateInputField}
|
|
placeholder="Location Name (Optional)"
|
|
placeholderTextColor="#999"
|
|
value={name}
|
|
onChangeText={setName}
|
|
returnKeyType="done"
|
|
/>
|
|
<View style={styles.coordinateValuesInputRow}>
|
|
<TextInput
|
|
style={styles.coordinateValueInput}
|
|
placeholder="X"
|
|
placeholderTextColor="#999"
|
|
keyboardType="numbers-and-punctuation"
|
|
value={xCoord}
|
|
onChangeText={setXCoord}
|
|
returnKeyType="done"
|
|
/>
|
|
<TextInput
|
|
style={styles.coordinateValueInput}
|
|
placeholder="Y"
|
|
placeholderTextColor="#999"
|
|
keyboardType="numbers-and-punctuation"
|
|
value={yCoord}
|
|
onChangeText={setYCoord}
|
|
returnKeyType="done"
|
|
/>
|
|
<TextInput
|
|
style={styles.coordinateValueInput}
|
|
placeholder="Z"
|
|
placeholderTextColor="#999"
|
|
keyboardType="numbers-and-punctuation"
|
|
value={zCoord}
|
|
onChangeText={setZCoord}
|
|
returnKeyType="done"
|
|
/>
|
|
</View>
|
|
<TouchableOpacity
|
|
onPress={handleAddCoordinate}
|
|
style={styles.addCoordinateButton}
|
|
>
|
|
<Text
|
|
style={styles.addCoordinateButtonText}
|
|
>
|
|
Save Coordinate
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{/* Coordinate List Display */}
|
|
<View style={styles.savedCoordinatesListContainer}>
|
|
{savedCoordinates.length === 0 ? (
|
|
<Text style={styles.placeholderText}>
|
|
No coordinates saved yet.
|
|
</Text>
|
|
) : (
|
|
savedCoordinates.map((coord) => (
|
|
<View
|
|
key={coord.id}
|
|
style={styles.coordinateListItem}
|
|
>
|
|
<View style={styles.coordinateTextInfo}>
|
|
<Text style={styles.coordinateListName}>
|
|
{coord.name}
|
|
</Text>
|
|
<Text style={styles.coordinateListValues}>
|
|
X: {coord.x}, Y: {coord.y}, Z:{" "}
|
|
{coord.z}
|
|
</Text>
|
|
</View>
|
|
<TouchableOpacity
|
|
onPress={() =>
|
|
handleDeleteCoordinate(coord.id)
|
|
}
|
|
style={styles.removeCoordinateButton}
|
|
>
|
|
<Text
|
|
style={styles.removeCoordinateButtonText}
|
|
>
|
|
X
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
))
|
|
)}
|
|
</View>
|
|
</ScrollView>
|
|
</KeyboardAvoidingView>
|
|
</SafeAreaView>
|
|
</View>
|
|
);
|
|
} |