252 lines
11 KiB
JavaScript
252 lines
11 KiB
JavaScript
import React from "react";
|
|
import { FlatList, Pressable, View } from "react-native";
|
|
import { ActivityIndicator, Button, Chip, Divider, Text, TextInput } from "react-native-paper";
|
|
import { useNavigation } from "@react-navigation/native";
|
|
import { useSnapshot } from "valtio";
|
|
import GlobalState from "../contexts/GlobalState.js";
|
|
import { BIBLE_BOOKS, createBibleToken, fetchBibleChapter, fetchBiblePassage, getBookChapterCount } from "../utils/bibleReferences.js";
|
|
import i18n from "../i18nMessages.js";
|
|
|
|
const BiblePicker = ({ route }) => {
|
|
const navigation = useNavigation();
|
|
const gState = useSnapshot(GlobalState);
|
|
const target = route?.params?.target || "post";
|
|
const [query, setQuery] = React.useState(route?.params?.initialReference || "");
|
|
const [chapter, setChapter] = React.useState("1");
|
|
const [verse, setVerse] = React.useState("1");
|
|
const [selectedBook, setSelectedBook] = React.useState("");
|
|
const [activeStep, setActiveStep] = React.useState("book");
|
|
const [preview, setPreview] = React.useState(null);
|
|
const [loadingPreview, setLoadingPreview] = React.useState(false);
|
|
const [loadingVerses, setLoadingVerses] = React.useState(false);
|
|
const [error, setError] = React.useState("");
|
|
const [verseOptions, setVerseOptions] = React.useState(["1"]);
|
|
const chapterSelectionTs = gState?.bibleChapterSelection?.ts;
|
|
|
|
const computedReference = React.useMemo(() => {
|
|
if (query.trim()) return query.trim();
|
|
if (!selectedBook) return "";
|
|
return `${selectedBook} ${chapter || "1"}:${verse || "1"}`;
|
|
}, [chapter, query, selectedBook, verse]);
|
|
|
|
const filteredBooks = React.useMemo(() => {
|
|
const q = query.toLowerCase().trim();
|
|
if (!q) return BIBLE_BOOKS;
|
|
return BIBLE_BOOKS.filter((book) => book.toLowerCase().includes(q));
|
|
}, [query]);
|
|
|
|
const selectedBookChapterCount = React.useMemo(() => getBookChapterCount(selectedBook), [selectedBook]);
|
|
const chapterOptions = React.useMemo(
|
|
() => Array.from({ length: selectedBookChapterCount }, (_v, i) => String(i + 1)),
|
|
[selectedBookChapterCount]
|
|
);
|
|
|
|
const addReferenceToCaller = (reference) => {
|
|
const cleanReference = String(reference || "").trim();
|
|
if (!cleanReference) return;
|
|
GlobalState.biblePickerSelection = {
|
|
token: createBibleToken(cleanReference),
|
|
reference: cleanReference,
|
|
target,
|
|
ts: Date.now(),
|
|
};
|
|
navigation.goBack();
|
|
};
|
|
|
|
const loadPreviewForReference = async (reference) => {
|
|
if (!reference) return;
|
|
setError("");
|
|
setLoadingPreview(true);
|
|
try {
|
|
const passage = await fetchBiblePassage(reference, i18n.locale);
|
|
setPreview(passage);
|
|
} catch (_err) {
|
|
setPreview(null);
|
|
setError(i18n.t("message.unableLoadPassage"));
|
|
} finally {
|
|
setLoadingPreview(false);
|
|
}
|
|
};
|
|
|
|
const loadVerseOptions = async (book, chapterNumber) => {
|
|
if (!book || !chapterNumber) {
|
|
setVerseOptions(["1"]);
|
|
return;
|
|
}
|
|
setLoadingVerses(true);
|
|
try {
|
|
const payload = await fetchBibleChapter(`${book} ${chapterNumber}`, i18n.locale);
|
|
const options = Array.isArray(payload?.verses)
|
|
? payload.verses.map((v) => String(v?.verse || "")).filter(Boolean)
|
|
: [];
|
|
setVerseOptions(options.length ? options : ["1"]);
|
|
} catch (_error) {
|
|
setVerseOptions(["1"]);
|
|
} finally {
|
|
setLoadingVerses(false);
|
|
}
|
|
};
|
|
|
|
React.useEffect(() => {
|
|
const picked = gState?.bibleChapterSelection;
|
|
if (!picked || !picked.book) return;
|
|
setSelectedBook(picked.book);
|
|
setChapter(String(picked.chapter || 1));
|
|
setVerse(String(picked.verse || 1));
|
|
setQuery("");
|
|
setActiveStep("verse");
|
|
loadVerseOptions(picked.book, String(picked.chapter || 1));
|
|
loadPreviewForReference(picked.reference || `${picked.book} ${picked.chapter || 1}:${picked.verse || 1}`);
|
|
GlobalState.bibleChapterSelection = null;
|
|
}, [chapterSelectionTs]);
|
|
|
|
return (
|
|
<View style={{ flex: 1, paddingHorizontal: 12, paddingTop: 12 }}>
|
|
<Text style={{ fontSize: 22, fontWeight: "700", marginBottom: 6 }}>{i18n.t("message.bibleReferenceTitle")}</Text>
|
|
<Text style={{ color: "#6b7280", marginBottom: 10 }}>
|
|
{target === "chat" ? i18n.t("message.biblePickerSubtitleChat") : i18n.t("message.biblePickerSubtitlePost")}
|
|
</Text>
|
|
|
|
<TextInput
|
|
mode="outlined"
|
|
value={query}
|
|
onChangeText={setQuery}
|
|
placeholder="John 3:16"
|
|
dense
|
|
/>
|
|
<View style={{ flexDirection: "row", marginTop: 8, justifyContent: "space-between" }}>
|
|
<Button mode="outlined" onPress={() => loadPreviewForReference(computedReference)} loading={loadingPreview}>
|
|
{i18n.t("message.preview")}
|
|
</Button>
|
|
<Button mode="contained" disabled={!computedReference} onPress={() => addReferenceToCaller(computedReference)}>
|
|
{i18n.t("message.useReference")}
|
|
</Button>
|
|
</View>
|
|
|
|
{computedReference ? <Text style={{ marginTop: 8, color: "#374151" }}>{i18n.t("message.selected")}: {computedReference}</Text> : null}
|
|
{preview?.text ? (
|
|
<Pressable
|
|
onPress={() => navigation.navigate("BibleChapter", { reference: computedReference, selectable: true })}
|
|
style={{ marginTop: 10, padding: 10, backgroundColor: "#f8fafc", borderRadius: 10 }}
|
|
>
|
|
<Text style={{ color: "#111827" }}>{preview.text.slice(0, 420)}</Text>
|
|
<Text style={{ marginTop: 4, color: "#4b5563", fontSize: 12 }}>
|
|
{preview.reference} ({preview.translation})
|
|
</Text>
|
|
<Text style={{ marginTop: 4, color: "#6b7280", fontSize: 12 }}>{i18n.t("message.tapPreviewPickVerse")}</Text>
|
|
</Pressable>
|
|
) : null}
|
|
{error ? <Text style={{ marginTop: 8, color: "#b91c1c" }}>{error}</Text> : null}
|
|
|
|
<Divider style={{ marginVertical: 12 }} />
|
|
|
|
<View style={{ flexDirection: "row", marginBottom: 10 }}>
|
|
<Button mode={activeStep === "book" ? "contained-tonal" : "text"} onPress={() => setActiveStep("book")}>
|
|
{i18n.t("message.book")}
|
|
</Button>
|
|
<Button
|
|
mode={activeStep === "chapter" ? "contained-tonal" : "text"}
|
|
disabled={!selectedBook}
|
|
onPress={() => setActiveStep("chapter")}
|
|
>
|
|
{i18n.t("message.chapter")}
|
|
</Button>
|
|
<Button
|
|
mode={activeStep === "verse" ? "contained-tonal" : "text"}
|
|
disabled={!selectedBook || !chapter}
|
|
onPress={() => setActiveStep("verse")}
|
|
>
|
|
{i18n.t("message.verse")}
|
|
</Button>
|
|
</View>
|
|
|
|
{activeStep === "book" ? (
|
|
<>
|
|
<Text style={{ fontSize: 14, fontWeight: "600", marginBottom: 8 }}>{i18n.t("message.books")}</Text>
|
|
<FlatList
|
|
data={filteredBooks}
|
|
numColumns={2}
|
|
keyExtractor={(item) => item}
|
|
renderItem={({ item }) => (
|
|
<Chip
|
|
selected={selectedBook === item}
|
|
style={{ marginRight: 6, marginBottom: 6, width: "48%" }}
|
|
onPress={async () => {
|
|
setSelectedBook(item);
|
|
setQuery("");
|
|
setChapter("1");
|
|
setVerse("1");
|
|
setActiveStep("chapter");
|
|
await loadVerseOptions(item, "1");
|
|
await loadPreviewForReference(`${item} 1:1`);
|
|
}}
|
|
>
|
|
{item}
|
|
</Chip>
|
|
)}
|
|
/>
|
|
</>
|
|
) : null}
|
|
|
|
{activeStep === "chapter" ? (
|
|
<>
|
|
<Text style={{ fontSize: 14, fontWeight: "600", marginBottom: 8 }}>
|
|
{i18n.t("message.chapters")} {selectedBook ? `(${selectedBook})` : ""}
|
|
</Text>
|
|
<FlatList
|
|
data={chapterOptions}
|
|
numColumns={6}
|
|
keyExtractor={(item) => item}
|
|
renderItem={({ item }) => (
|
|
<Chip
|
|
selected={chapter === item}
|
|
style={{ marginRight: 6, marginBottom: 6, minWidth: 44 }}
|
|
onPress={async () => {
|
|
setChapter(item);
|
|
setVerse("1");
|
|
setActiveStep("verse");
|
|
await loadVerseOptions(selectedBook, item);
|
|
await loadPreviewForReference(`${selectedBook} ${item}:1`);
|
|
}}
|
|
>
|
|
{item}
|
|
</Chip>
|
|
)}
|
|
/>
|
|
</>
|
|
) : null}
|
|
|
|
{activeStep === "verse" ? (
|
|
<>
|
|
<Text style={{ fontSize: 14, fontWeight: "600", marginBottom: 8 }}>
|
|
{i18n.t("message.verses")} {selectedBook && chapter ? `(${selectedBook} ${chapter})` : ""}
|
|
</Text>
|
|
{loadingVerses ? (
|
|
<ActivityIndicator style={{ marginTop: 8 }} />
|
|
) : (
|
|
<FlatList
|
|
data={verseOptions}
|
|
numColumns={8}
|
|
keyExtractor={(item) => item}
|
|
renderItem={({ item }) => (
|
|
<Chip
|
|
selected={verse === item}
|
|
style={{ marginRight: 6, marginBottom: 6, minWidth: 40 }}
|
|
onPress={async () => {
|
|
setVerse(item);
|
|
await loadPreviewForReference(`${selectedBook} ${chapter || "1"}:${item}`);
|
|
}}
|
|
>
|
|
{item}
|
|
</Chip>
|
|
)}
|
|
/>
|
|
)}
|
|
</>
|
|
) : null}
|
|
</View>
|
|
);
|
|
};
|
|
|
|
export default BiblePicker;
|