minecraft-tools/app/motd.tsx

377 lines
14 KiB
TypeScript
Raw Permalink Normal View History

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<string>("");
const [savedMotds, setSavedMotds] = useState<string[]>([]);
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(
<Text key={Math.random()} style={style}>
{currentText}
</Text>
);
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(
<Text key={Math.random()} style={style}>
{currentText}
</Text>
);
}
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 (
<View style={styles.converterScreenRoot}>
<ImageBackground
source={motdBgImage}
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}>
MOTD Creator
</Text>
{/* MOTD Input */}
<TextInput
style={styles.textInput}
placeholder="Enter your MOTD text here..."
placeholderTextColor="#ccc"
multiline
value={motdText}
onChangeText={setMotdText}
/>
{/* Color Codes */}
<Text style={styles.sectionTitle}>Color Codes:</Text>
<View style={styles.buttonRow}>
{Object.entries(MINECRAFT_COLORS).map(
([codeChar, colorHex]) => (
<TouchableOpacity
key={codeChar}
style={[
styles.colorButton,
{ backgroundColor: colorHex }
]}
onPress={() => applyCode(codeChar)}
>
<Text style={styles.buttonText}>
§{codeChar}
</Text>
</TouchableOpacity>
)
)}
</View>
{/* Formatting Codes */}
<Text style={styles.sectionTitle}>
Formatting Codes:
</Text>
<View style={styles.buttonRow}>
{[
{ 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) => (
<TouchableOpacity
key={format.code}
style={styles.formatButton}
onPress={() => applyCode(format.code)}
>
<Text style={styles.buttonText}>
§{format.code} {format.name}
</Text>
</TouchableOpacity>
))}
</View>
{/* MOTD Preview */}
<Text style={styles.sectionTitle}>MOTD Preview:</Text>
<View style={styles.previewContainer}>
<Text style={styles.previewBaseText}>
{previewMotdComponents}
</Text>
</View>
{/* Save MOTD Button */}
<TouchableOpacity
style={styles.saveButton}
onPress={saveMotd}
>
<Text style={styles.saveButtonText}>Save MOTD</Text>
</TouchableOpacity>
{/* Saved MOTDs */}
<Text style={styles.sectionTitle}>Saved MOTDs:</Text>
{savedMotds.length === 0 ? (
<Text style={styles.noMotdsText}>
No MOTDs saved yet.
</Text>
) : (
<View style={styles.savedMotdsContainer}>
{savedMotds.map((motd, index) => (
<View
key={index}
style={styles.savedMotdItem}
>
<Text style={styles.savedMotdText}>
{motd}
</Text>
<TouchableOpacity
onPress={() => deleteMotd(index)}
style={styles.deleteButton}
>
<Text
style={styles.deleteButtonText}
>
Delete
</Text>
</TouchableOpacity>
</View>
))}
</View>
)}
</ScrollView>
</KeyboardAvoidingView>
</SafeAreaView>
</View>
);
}