feat: Add coordinate-converter
Signed-off-by: SindreKjelsrud <sindre@kjelsrud.dev>
This commit is contained in:
parent
978e60c6d3
commit
de79281681
3 changed files with 312 additions and 2 deletions
|
@ -4,6 +4,7 @@ export default function RootLayout() {
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
<Stack.Screen name="index" options={{ headerShown: false }} />
|
<Stack.Screen name="index" options={{ headerShown: false }} />
|
||||||
|
<Stack.Screen name="coordinate-converter" options={{ headerShown: false }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
193
app/coordinate-converter.tsx
Normal file
193
app/coordinate-converter.tsx
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
import { useRouter } from "expo-router";
|
||||||
|
import { useCallback, useState } from "react";
|
||||||
|
import {
|
||||||
|
ImageBackground,
|
||||||
|
KeyboardAvoidingView,
|
||||||
|
Platform,
|
||||||
|
ScrollView,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
TouchableOpacity,
|
||||||
|
View,
|
||||||
|
} from "react-native";
|
||||||
|
import { SafeAreaView } from "react-native-safe-area-context";
|
||||||
|
import { styles } from "./styles";
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// Define your background image URI
|
||||||
|
const converterBgImage = {
|
||||||
|
uri: "https://s1.qwant.com/thumbr/474x296/1/c/30947af0647acb7b3ef1eb2b5697b5dde49d5468269c918e41bb86286b007e/OIP.MpGUIDhV0CzgCK1mQYFbHQHaEo.jpg?u=https%3A%2F%2Ftse.mm.bing.net%2Fth%2Fid%2FOIP.MpGUIDhV0CzgCK1mQYFbHQHaEo%3Fr%3D0%26pid%3DApi&q=0&b=1&p=0&a=0",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CoordinateConverter() {
|
||||||
|
const [overworldX, setOverworldX] = useState("");
|
||||||
|
const [overworldZ, setOverworldZ] = useState("");
|
||||||
|
const [netherX, setNetherX] = useState("");
|
||||||
|
const [netherZ, setNetherZ] = useState("");
|
||||||
|
|
||||||
|
const safeParseFloat = (value: string) => {
|
||||||
|
const num = parseFloat(value);
|
||||||
|
return isNaN(num) ? undefined : num;
|
||||||
|
};
|
||||||
|
|
||||||
|
const convertOWToNether = useCallback(
|
||||||
|
(xStr: string, zStr: string) => {
|
||||||
|
const x = safeParseFloat(xStr);
|
||||||
|
const z = safeParseFloat(zStr);
|
||||||
|
if (x !== undefined && z !== undefined) {
|
||||||
|
setNetherX(Math.floor(x / 8).toString());
|
||||||
|
setNetherZ(Math.floor(z / 8).toString());
|
||||||
|
} else {
|
||||||
|
setNetherX("");
|
||||||
|
setNetherZ("");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const convertNetherToOW = useCallback(
|
||||||
|
(xStr: string, zStr: string) => {
|
||||||
|
const x = safeParseFloat(xStr);
|
||||||
|
const z = safeParseFloat(zStr);
|
||||||
|
if (x !== undefined && z !== undefined) {
|
||||||
|
setOverworldX(Math.floor(x * 8).toString());
|
||||||
|
setOverworldZ(Math.floor(z * 8).toString());
|
||||||
|
} else {
|
||||||
|
setOverworldX("");
|
||||||
|
setOverworldZ("");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOverworldXChange = useCallback(
|
||||||
|
(text: string) => {
|
||||||
|
setOverworldX(text);
|
||||||
|
convertOWToNether(text, overworldZ);
|
||||||
|
},
|
||||||
|
[overworldZ, convertOWToNether]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOverworldZChange = useCallback(
|
||||||
|
(text: string) => {
|
||||||
|
setOverworldZ(text);
|
||||||
|
convertOWToNether(overworldX, text);
|
||||||
|
},
|
||||||
|
[overworldX, convertOWToNether]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleNetherXChange = useCallback(
|
||||||
|
(text: string) => {
|
||||||
|
setNetherX(text);
|
||||||
|
convertNetherToOW(text, netherZ);
|
||||||
|
},
|
||||||
|
[netherZ, convertNetherToOW]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleNetherZChange = useCallback(
|
||||||
|
(text: string) => {
|
||||||
|
setNetherZ(text);
|
||||||
|
convertNetherToOW(netherX, text);
|
||||||
|
},
|
||||||
|
[netherX, convertNetherToOW]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleClear = useCallback(() => {
|
||||||
|
setOverworldX("");
|
||||||
|
setOverworldZ("");
|
||||||
|
setNetherX("");
|
||||||
|
setNetherZ("");
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.converterSafeArea}>
|
||||||
|
<ImageBackground
|
||||||
|
source={converterBgImage}
|
||||||
|
style={styles.converterBackgroundImage}
|
||||||
|
resizeMode="cover"
|
||||||
|
>
|
||||||
|
<View style={styles.converterBackgroundOverlay}>
|
||||||
|
<KeyboardAvoidingView
|
||||||
|
style={styles.converterContainer}
|
||||||
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||||
|
>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => router.back()} // Correct!
|
||||||
|
style={styles.backButton}
|
||||||
|
>
|
||||||
|
<Text style={styles.backButtonText}>← Go Back</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<ScrollView contentContainerStyle={styles.converterScrollContent}>
|
||||||
|
<Text style={styles.converterTitle}>Coordinate Converter</Text>
|
||||||
|
|
||||||
|
{/* Overworld Row */}
|
||||||
|
<View style={styles.dimensionContainer}>
|
||||||
|
<Text style={styles.dimensionTitle}>Overworld (X, Z)</Text>
|
||||||
|
<View style={styles.coordinatesRow}>
|
||||||
|
<View style={styles.coordInputGroup}>
|
||||||
|
<Text style={styles.coordInputLabel}>X:</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.coordTextInput}
|
||||||
|
keyboardType="numeric"
|
||||||
|
placeholder="Enter X"
|
||||||
|
placeholderTextColor="#ccc"
|
||||||
|
value={overworldX}
|
||||||
|
onChangeText={handleOverworldXChange}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.coordInputGroup}>
|
||||||
|
<Text style={styles.coordInputLabel}>Z:</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.coordTextInput}
|
||||||
|
keyboardType="numeric"
|
||||||
|
placeholder="Enter Z"
|
||||||
|
placeholderTextColor="#ccc"
|
||||||
|
value={overworldZ}
|
||||||
|
onChangeText={handleOverworldZChange}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Nether Row */}
|
||||||
|
<View style={styles.dimensionContainer}>
|
||||||
|
<Text style={styles.dimensionTitle}>Nether (X, Z)</Text>
|
||||||
|
<View style={styles.coordinatesRow}>
|
||||||
|
<View style={styles.coordInputGroup}>
|
||||||
|
<Text style={styles.coordInputLabel}>X:</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.coordTextInput}
|
||||||
|
keyboardType="numeric"
|
||||||
|
placeholder="Enter X"
|
||||||
|
placeholderTextColor="#ccc"
|
||||||
|
value={netherX}
|
||||||
|
onChangeText={handleNetherXChange}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.coordInputGroup}>
|
||||||
|
<Text style={styles.coordInputLabel}>Z:</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.coordTextInput}
|
||||||
|
keyboardType="numeric"
|
||||||
|
placeholder="Enter Z"
|
||||||
|
placeholderTextColor="#ccc"
|
||||||
|
value={netherZ}
|
||||||
|
onChangeText={handleNetherZChange}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.clearButton}
|
||||||
|
onPress={handleClear}
|
||||||
|
>
|
||||||
|
<Text style={styles.clearButtonText}>Clear All</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</ScrollView>
|
||||||
|
</KeyboardAvoidingView>
|
||||||
|
</View>
|
||||||
|
</ImageBackground>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
120
app/styles.ts
120
app/styles.ts
|
@ -2,11 +2,12 @@
|
||||||
import { StyleSheet } from "react-native";
|
import { StyleSheet } from "react-native";
|
||||||
|
|
||||||
export const styles = StyleSheet.create({
|
export const styles = StyleSheet.create({
|
||||||
container: {
|
// --- Global / Reusable Styles (or main index page specific) ---
|
||||||
|
appContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: "#2e2e2e",
|
backgroundColor: "#2e2e2e",
|
||||||
},
|
},
|
||||||
title: {
|
mainTitle: {
|
||||||
fontSize: 38,
|
fontSize: 38,
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
|
@ -50,4 +51,119 @@ export const styles = StyleSheet.create({
|
||||||
textShadowOffset: { width: 1, height: 1 },
|
textShadowOffset: { width: 1, height: 1 },
|
||||||
textShadowRadius: 3,
|
textShadowRadius: 3,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// --- Coordinate Converter Specific Styles (Adjusted and New) ---
|
||||||
|
converterSafeArea: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
converterBackgroundImage: {
|
||||||
|
flex: 1,
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
converterBackgroundOverlay: {
|
||||||
|
...StyleSheet.absoluteFillObject,
|
||||||
|
backgroundColor: "rgba(0, 0, 0, 0.6)",
|
||||||
|
},
|
||||||
|
converterContainer: {
|
||||||
|
flex: 1,
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
converterScrollContent: {
|
||||||
|
flexGrow: 1,
|
||||||
|
padding: 20,
|
||||||
|
alignItems: "center",
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
converterTitle: {
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: "#fff",
|
||||||
|
marginBottom: 30,
|
||||||
|
marginTop: 20,
|
||||||
|
textShadowColor: "rgba(0, 0, 0, 0.7)",
|
||||||
|
textShadowOffset: { width: 1, height: 1 },
|
||||||
|
textShadowRadius: 3,
|
||||||
|
},
|
||||||
|
dimensionContainer: {
|
||||||
|
width: "90%",
|
||||||
|
backgroundColor: "rgba(58, 58, 58, 0.7)",
|
||||||
|
borderRadius: 10,
|
||||||
|
borderColor: "#6a5acd",
|
||||||
|
borderWidth: 2,
|
||||||
|
padding: 15,
|
||||||
|
marginBottom: 20,
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
dimensionTitle: {
|
||||||
|
color: "#fff",
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: "bold",
|
||||||
|
marginBottom: 15,
|
||||||
|
textShadowColor: "rgba(0, 0, 0, 0.5)",
|
||||||
|
textShadowOffset: { width: 1, height: 1 },
|
||||||
|
textShadowRadius: 2,
|
||||||
|
},
|
||||||
|
coordinatesRow: {
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
coordInputGroup: {
|
||||||
|
flex: 1,
|
||||||
|
marginHorizontal: 5,
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
coordInputLabel: {
|
||||||
|
color: "#e0e0e0",
|
||||||
|
fontSize: 16,
|
||||||
|
marginBottom: 5,
|
||||||
|
fontWeight: "500",
|
||||||
|
textShadowColor: "rgba(0, 0, 0, 0.5)",
|
||||||
|
textShadowOffset: { width: 1, height: 1 },
|
||||||
|
textShadowRadius: 2,
|
||||||
|
},
|
||||||
|
coordTextInput: {
|
||||||
|
backgroundColor: "rgba(58, 58, 58, 0.8)",
|
||||||
|
color: "#fff",
|
||||||
|
paddingVertical: 10,
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
borderRadius: 8,
|
||||||
|
fontSize: 18,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: "#555",
|
||||||
|
width: "100%",
|
||||||
|
textAlign: "center",
|
||||||
|
},
|
||||||
|
|
||||||
|
clearButton: {
|
||||||
|
backgroundColor: "#d32f2f",
|
||||||
|
paddingVertical: 12,
|
||||||
|
paddingHorizontal: 25,
|
||||||
|
borderRadius: 8,
|
||||||
|
marginTop: 30,
|
||||||
|
},
|
||||||
|
clearButtonText: {
|
||||||
|
color: "#fff",
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
|
|
||||||
|
backButton: {
|
||||||
|
alignSelf: "flex-start",
|
||||||
|
marginTop: 10,
|
||||||
|
marginBottom: 20,
|
||||||
|
paddingVertical: 8,
|
||||||
|
paddingHorizontal: 15,
|
||||||
|
backgroundColor: "rgba(106, 90, 205, 0.7)",
|
||||||
|
borderRadius: 8,
|
||||||
|
marginLeft: 10,
|
||||||
|
},
|
||||||
|
backButtonText: {
|
||||||
|
color: "#fff",
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
});
|
});
|
Loading…
Add table
Reference in a new issue