diff --git a/app/_layout.tsx b/app/_layout.tsx index f454520..af5aaa8 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -16,6 +16,7 @@ export default function RootLayout() { + ); } \ No newline at end of file diff --git a/app/motd.tsx b/app/motd.tsx new file mode 100644 index 0000000..ea85afc --- /dev/null +++ b/app/motd.tsx @@ -0,0 +1,377 @@ +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 motdBgImage = require("../assets/images/motd.png"); + +// Define a map for Minecraft color codes to React Native colors +const MINECRAFT_COLORS: { [key: string]: string } = { + "0": "#000000", // Black + "1": "#0000AA", // Dark Blue + "2": "#00AA00", // Dark Green + "3": "#00AAAA", // Dark Aqua + "4": "#AA0000", // Dark Red + "5": "#AA00AA", // Dark Purple + "6": "#FFAA00", // Gold + "7": "#AAAAAA", // Gray + "8": "#555555", // Dark Gray + "9": "#5555FF", // Blue + a: "#55FF55", // Green + b: "#55FFFF", // Aqua + c: "#FF5555", // Red + d: "#FF55FF", // Light Purple + e: "#FFFF55", // Yellow + f: "#FFFFFF" // White +}; + +export default function MotdCreator() { + const router = useRouter(); + const [motdText, setMotdText] = useState(""); + const [savedMotds, setSavedMotds] = useState([]); + const [previewMotdComponents, setPreviewMotdComponents] = useState< + React.ReactNode[] + >([]); + + // Define a unique key for AsyncStorage + const STORAGE_KEY = "@minecraft_motd_list"; + + // Persistence Logic + useEffect(() => { + loadMotds(); + }, []); + + useEffect(() => { + saveMotds(); + }, [savedMotds]); + + useEffect(() => { + generateMotdPreview(); + }, [motdText]); + + // Async function to load MOTDs from AsyncStorage + const loadMotds = async () => { + try { + const jsonValue = await AsyncStorage.getItem(STORAGE_KEY); + if (jsonValue != null) { + const parsedMotds = JSON.parse(jsonValue); + if (Array.isArray(parsedMotds)) { + setSavedMotds(parsedMotds); + } else { + console.warn( + "Loaded data is not an array, initializing empty MOTD list." + ); + setSavedMotds([]); + } + } + } catch (e) { + console.error("Failed to load MOTDs:", e); + Alert.alert("Error", "Failed to load your MOTD list."); + } + }; + + // Async function to save MOTDs to AsyncStorage + const saveMotds = async () => { + try { + const jsonValue = JSON.stringify(savedMotds); + await AsyncStorage.setItem(STORAGE_KEY, jsonValue); + } catch (e) { + console.error("Failed to save MOTDs:", e); + Alert.alert("Error", "Failed to save your MOTD list."); + } + }; + + // Function to apply Minecraft color codes + const applyCode = (code: string) => { + setMotdText((prevText) => prevText + "§" + code); + }; + + // Function to generate a preview of the MOTD with styled Text components + const generateMotdPreview = () => { + const parts: React.ReactNode[] = []; + let currentText = ""; + let currentColor: string | undefined = undefined; + let isBold = false; + let isItalic = false; + let isUnderlined = false; + let isStrikethrough = false; + let isObfuscated = false; + + const regex = /(§[0-9a-fk-or])|([^§]+)/g; + let match; + + while ((match = regex.exec(motdText)) !== null) { + const code = match[1]; + const textContent = match[2]; + + if (currentText.length > 0) { + const style: any = { + color: currentColor || MINECRAFT_COLORS.f + }; + if (isBold) style.fontWeight = "bold"; + if (isItalic) style.fontStyle = "italic"; + if (isUnderlined) + style.textDecorationLine = "underline"; + if (isStrikethrough) + style.textDecorationLine = style.textDecorationLine + ? style.textDecorationLine + " line-through" + : "line-through"; + + parts.push( + + {currentText} + + ); + currentText = ""; // Reset current text + } + + if (code) { + const codeChar = code.charAt(1); + + // Apply color + if (MINECRAFT_COLORS[codeChar]) { + currentColor = MINECRAFT_COLORS[codeChar]; + } + // Apply formatting + switch (codeChar) { + case "l": // Bold + isBold = true; + break; + case "m": // Strikethrough + isStrikethrough = true; + break; + case "n": // Underline + isUnderlined = true; + break; + case "o": // Italic + isItalic = true; + break; + case "k": // Obfuscated + isObfuscated = true; // Mark as obfuscated, but don't change text + break; + case "r": // Reset + currentColor = undefined; + isBold = false; + isItalic = false; + isUnderlined = false; + isStrikethrough = false; + isObfuscated = false; + break; + } + } else if (textContent) { + currentText += textContent; + } + } + + // Add any remaining text after the last code or if no codes were found + if (currentText.length > 0) { + const style: any = { + color: currentColor || MINECRAFT_COLORS.f + }; + if (isBold) style.fontWeight = "bold"; + if (isItalic) style.fontStyle = "italic"; + if (isUnderlined) style.textDecorationLine = "underline"; + if (isStrikethrough) + style.textDecorationLine = style.textDecorationLine + ? style.textDecorationLine + " line-through" + : "line-through"; + + parts.push( + + {currentText} + + ); + } + + setPreviewMotdComponents(parts); + }; + + const saveMotd = () => { + if (motdText.trim().length > 0) { + setSavedMotds((prevMotds) => [...prevMotds, motdText.trim()]); + setMotdText(""); // Clear input after saving + } else { + Alert.alert("Empty MOTD", "Please enter some text to save."); + } + }; + + const deleteMotd = (index: number) => { + const motdToDelete = savedMotds[index]; + + Alert.alert( + "Delete MOTD", + `Are you sure you want to delete "${motdToDelete}"?`, + [ + { + text: "Cancel", + onPress: () => console.log("Delete Cancelled"), + style: "cancel" + }, + { + text: "Delete", + onPress: () => { + const updatedMotds = savedMotds.filter( + (_, i) => i !== index + ); + setSavedMotds(updatedMotds); + console.log("MOTD deleted:", motdToDelete); + }, + style: "destructive" + } + ], + { cancelable: true } + ); + }; + + return ( + + + + + + + + + {/* Custom Back Button */} + router.back()} + style={styles.backButton} + > + + ← Go Back + + + + + MOTD Creator + + + {/* MOTD Input */} + + + {/* Color Codes */} + Color Codes: + + {Object.entries(MINECRAFT_COLORS).map( + ([codeChar, colorHex]) => ( + applyCode(codeChar)} + > + + §{codeChar} + + + ) + )} + + + {/* Formatting Codes */} + + Formatting Codes: + + + {[ + { code: "k", name: "Obfuscated" }, + { code: "l", name: "Bold" }, + { code: "m", name: "Strikethrough" }, + { code: "n", name: "Underline" }, + { code: "o", name: "Italic" }, + { code: "r", name: "Reset" } + ].map((format) => ( + applyCode(format.code)} + > + + §{format.code} {format.name} + + + ))} + + + {/* MOTD Preview */} + MOTD Preview: + + + {previewMotdComponents} + + + + {/* Save MOTD Button */} + + Save MOTD + + + {/* Saved MOTDs */} + Saved MOTDs: + {savedMotds.length === 0 ? ( + + No MOTDs saved yet. + + ) : ( + + {savedMotds.map((motd, index) => ( + + + {motd} + + deleteMotd(index)} + style={styles.deleteButton} + > + + Delete + + + + ))} + + )} + + + + + ); +} \ No newline at end of file diff --git a/app/styles.ts b/app/styles.ts index 7f7357d..068c052 100644 --- a/app/styles.ts +++ b/app/styles.ts @@ -354,5 +354,113 @@ export const styles = StyleSheet.create({ fontSize: 18, fontWeight: "bold", fontFamily: "Minecraft" + }, + + // MOTD + textInput: { + backgroundColor: "rgba(255, 255, 255, 0.1)", + borderRadius: 10, + padding: 15, + fontSize: 16, + color: "#fff", + marginBottom: 20, + minHeight: 80, + textAlignVertical: "top", + borderColor: "#4CAF50", + borderWidth: 1 + }, + sectionTitle: { + fontSize: 20, + fontWeight: "bold", + color: "#fff", + marginBottom: 10, + marginTop: 15 + }, + buttonRow: { + flexDirection: "row", + flexWrap: "wrap", + justifyContent: "center", + marginBottom: 15 + }, + colorButton: { + backgroundColor: "#1e88e5", + paddingVertical: 8, + paddingHorizontal: 12, + borderRadius: 5, + margin: 5 + }, + formatButton: { + backgroundColor: "#ffb300", + paddingVertical: 8, + paddingHorizontal: 12, + borderRadius: 5, + margin: 5 + }, + buttonText: { + color: "#fff", + fontWeight: "bold", + fontSize: 14 + }, + previewContainer: { + backgroundColor: "#000", + padding: 15, + borderRadius: 10, + minHeight: 60, + justifyContent: "center", + marginBottom: 20, + borderColor: "#555", + borderWidth: 1, + }, + previewBaseText: { + fontSize: 18, + }, + saveButton: { + backgroundColor: "#4CAF50", + paddingVertical: 15, + borderRadius: 10, + alignItems: "center", + marginBottom: 20 + }, + saveButtonText: { + color: "#fff", + fontSize: 18, + fontWeight: "bold" + }, + noMotdsText: { + color: "#ccc", + textAlign: "center", + fontStyle: "italic", + marginTop: 10 + }, + savedMotdsContainer: { + backgroundColor: "rgba(255, 255, 255, 0.1)", + borderRadius: 10, + padding: 10 + }, + savedMotdItem: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + backgroundColor: "rgba(0, 0, 0, 0.4)", + padding: 10, + borderRadius: 8, + marginBottom: 8, + borderWidth: 1, + borderColor: "#333" + }, + savedMotdText: { + color: "#eee", + fontSize: 16, + flexShrink: 1 + }, + deleteButton: { + backgroundColor: "#e53935", + paddingVertical: 5, + paddingHorizontal: 10, + borderRadius: 5 + }, + deleteButtonText: { + color: "#fff", + fontWeight: "bold" } }); \ No newline at end of file diff --git a/assets/images/motd.png b/assets/images/motd.png new file mode 100644 index 0000000..8d47de4 Binary files /dev/null and b/assets/images/motd.png differ