Add Bible picker flow and chapter navigation UI

This commit is contained in:
Adolfo Reyna
2026-02-24 15:56:48 -05:00
parent ba28289783
commit ccfeed3c92
9 changed files with 865 additions and 12 deletions

View File

@@ -0,0 +1,118 @@
import React, { useMemo, useState } from "react";
import { StyleSheet, Text, View } from "react-native";
import { ActivityIndicator, Chip } from "react-native-paper";
import { useNavigation } from "@react-navigation/native";
import { extractBibleReferences, fetchBiblePassage } from "../utils/bibleReferences.js";
const BibleEmbeddedView = ({ content = "", compact = false, openChapterOnPress = false }) => {
const navigation = useNavigation();
const references = useMemo(() => extractBibleReferences(content), [content]);
const [selectedRef, setSelectedRef] = useState("");
const [byReference, setByReference] = useState({});
if (!references.length) return null;
const handleSelectReference = async (reference) => {
if (openChapterOnPress) {
navigation.navigate("BibleChapter", { reference });
return;
}
setSelectedRef(reference);
const current = byReference[reference];
if (current?.loading || current?.text || current?.error) return;
setByReference((prev) => ({ ...prev, [reference]: { loading: true } }));
try {
const data = await fetchBiblePassage(reference);
setByReference((prev) => ({ ...prev, [reference]: { loading: false, ...data } }));
} catch (_error) {
setByReference((prev) => ({ ...prev, [reference]: { loading: false, error: true } }));
}
};
const selectedData = selectedRef ? byReference[selectedRef] : null;
return (
<View style={[styles.container, compact ? styles.compactContainer : null]}>
<Text style={styles.label}>Bible</Text>
<View style={styles.chipsWrap}>
{references.map((reference) => (
<Chip
key={reference}
mode={selectedRef === reference ? "flat" : "outlined"}
selected={selectedRef === reference}
compact
style={styles.chip}
onPress={() => handleSelectReference(reference)}
>
{reference}
</Chip>
))}
</View>
{selectedData?.loading ? <ActivityIndicator size="small" style={styles.loader} /> : null}
{selectedData?.text ? (
<View style={styles.previewBox}>
<Text style={styles.previewText}>{selectedData.text.slice(0, compact ? 160 : 280)}</Text>
<Text style={styles.previewMeta}>
{selectedData.reference} ({selectedData.translation})
</Text>
</View>
) : null}
{selectedData?.error ? <Text style={styles.errorText}>Unable to load this passage.</Text> : null}
</View>
);
};
export default BibleEmbeddedView;
const styles = StyleSheet.create({
container: {
paddingTop: 6,
paddingHorizontal: 8,
},
compactContainer: {
paddingTop: 4,
paddingHorizontal: 0,
},
label: {
fontSize: 12,
color: "#4b5563",
fontWeight: "700",
marginBottom: 4,
},
chipsWrap: {
flexDirection: "row",
flexWrap: "wrap",
},
chip: {
marginRight: 6,
marginBottom: 6,
backgroundColor: "#f8fafc",
},
loader: {
alignSelf: "flex-start",
},
previewBox: {
marginTop: 2,
backgroundColor: "#f8fafc",
borderWidth: 1,
borderColor: "#e5e7eb",
borderRadius: 10,
padding: 8,
},
previewText: {
fontSize: 13,
color: "#111827",
},
previewMeta: {
marginTop: 4,
fontSize: 11,
color: "#6b7280",
fontWeight: "600",
},
errorText: {
marginTop: 2,
fontSize: 12,
color: "#b91c1c",
},
});

View File

@@ -14,6 +14,8 @@ import ProfilePhotoCircle from './ProfilePhotoCircle.js';
import { posthog } from './../PostHog.js';
import { useNavigation } from '@react-navigation/native';
import ParsedText from 'react-native-parsed-text';
import BibleEmbeddedView from './BibleEmbeddedView.js';
import { stripBibleTokens } from '../utils/bibleReferences.js';
let Post = (props) => {
@@ -30,7 +32,7 @@ let Post = (props) => {
const SWIPE_WIDTH = 86;
let toProfileText = post.toProfile && post.toProfile !== post.profileid ?
<ProfilePhotoCircle profileid={post.toProfile} small={true} /> : undefined;
let cleanContent = post.content.replace(/@[A-z]+:.+\w/g, '').trim();
let cleanContent = stripInlineTags(stripBibleTokens(post.content));
const navigation = useNavigation();
//cleanContent = convertLinks(cleanContent);
const newComentAdded = (commentData) => {
@@ -172,6 +174,9 @@ let Post = (props) => {
{cleanContent}
</ParsedText>
</Pressable>
<View style={{ paddingLeft: 40, paddingRight: 8 }}>
<BibleEmbeddedView content={post.content} openChapterOnPress />
</View>
<View
onStartShouldSetResponderCapture={() => {
@@ -356,3 +361,10 @@ const styles = StyleSheet.create({
textDecorationLine: 'underline',
},
});
const stripInlineTags = (content = "") => {
return String(content || "")
.replace(/@[A-Za-z]+:[^\s]+/g, "")
.replace(/[ \t]{2,}/g, " ")
.replace(/[ \t]+\n/g, "\n")
.trim();
};