@replyke/ui-core-react-native
Version:
Replyke: Build interactive apps with social features like comments, votes, feeds, user lists, notifications, and more.
229 lines • 11.7 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = GiphyContainer;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const react_native_1 = require("react-native");
const react_native_gesture_handler_1 = require("react-native-gesture-handler");
const icons_1 = require("../icons");
const getImageComponent_1 = require("../helpers/getImageComponent");
const MAX_ITEMS = 60; // Define the maximum number of items to load
const FETCH_LIMIT = 30;
function GiphyContainer({ giphyApiKey, onClickBack, onSelectGif, visible, }) {
// Dynamically get the correct Image component and whether it is expo-image.
const { ImageComponent, isExpo } = (0, getImageComponent_1.getImageComponent)();
const [gifs, setGifs] = (0, react_1.useState)([]);
const [loading, setLoading] = (0, react_1.useState)(false);
const [loadingMore, setLoadingMore] = (0, react_1.useState)(false);
const [currentOffset, setCurrentOffset] = (0, react_1.useState)(0);
const [totalCount, setTotalCount] = (0, react_1.useState)(0);
const [flatListWidth, setFlatListWidth] = (0, react_1.useState)(0); // Track the width of the FlatList
// Search states & debouncing
const [query, setQuery] = (0, react_1.useState)("");
const [debouncedQuery, setDebouncedQuery] = (0, react_1.useState)("");
// Refs to track fetching state and gifs length
const isFetchingRef = (0, react_1.useRef)(false);
const gifsLengthRef = (0, react_1.useRef)(0);
// Debounce effect (like in the web version)
(0, react_1.useEffect)(() => {
const handler = setTimeout(() => {
setDebouncedQuery(query);
}, 500); // 500ms debounce
return () => clearTimeout(handler);
}, [query]);
// Update gifsLengthRef when gifs change
(0, react_1.useEffect)(() => {
gifsLengthRef.current = gifs.length;
}, [gifs]);
// Fetch function that decides between trending or search
const fetchGifs = (0, react_1.useCallback)(async (offset = 0) => {
// Prevent multiple fetches
if (isFetchingRef.current) {
return;
}
// Check if we've reached the maximum limit
if (Math.max(gifsLengthRef.current, offset) >= MAX_ITEMS) {
return;
}
isFetchingRef.current = true;
if (offset === 0) {
setLoading(true);
setLoadingMore(false);
}
else {
setLoadingMore(true);
}
let url = "";
const trimmed = debouncedQuery.trim();
if (trimmed.length === 0) {
// Fetch trending if no query
url = `https://api.giphy.com/v1/gifs/trending?api_key=${giphyApiKey}&limit=${FETCH_LIMIT}&offset=${offset}`;
}
else {
// Fetch search results
url = `https://api.giphy.com/v1/gifs/search?api_key=${giphyApiKey}&q=${encodeURIComponent(trimmed)}&limit=${FETCH_LIMIT}&offset=${offset}`;
}
try {
const res = await fetch(url);
const json = (await res.json());
if (json.meta.status !== 200) {
console.error("API Error:", json.meta.msg);
return;
}
setTotalCount(json.pagination.total_count);
setCurrentOffset(json.pagination.offset + json.pagination.count);
// Calculate how many items we can still fetch
const remainingItems = MAX_ITEMS - gifsLengthRef.current;
const fetchedItems = json.data.slice(0, remainingItems);
if (offset === 0) {
setGifs(fetchedItems);
}
else {
setGifs((prevGifs) => [...prevGifs, ...fetchedItems]);
}
}
catch (err) {
console.error("Fetch Error:", err);
}
finally {
if (offset === 0) {
setLoading(false);
}
else {
setLoadingMore(false);
}
isFetchingRef.current = false;
}
}, [debouncedQuery, giphyApiKey]);
// Fetch more GIFs when reaching the end
const fetchMoreGifs = (0, react_1.useCallback)(() => {
if (loading ||
loadingMore ||
gifsLengthRef.current >= totalCount ||
gifsLengthRef.current >= MAX_ITEMS ||
isFetchingRef.current) {
return;
}
fetchGifs(currentOffset);
}, [loading, loadingMore, totalCount, currentOffset, fetchGifs]);
// Reset search and pagination when visibility or query changes
(0, react_1.useEffect)(() => {
if (!visible) {
// Reset all states when not visible
setQuery("");
setDebouncedQuery("");
setGifs([]);
setCurrentOffset(0);
setTotalCount(0);
setLoading(false);
setLoadingMore(false);
isFetchingRef.current = false;
}
else {
// When visibility changes to true, reset offset and fetch initial gifs
setCurrentOffset(0);
setTotalCount(0);
setGifs([]);
fetchGifs(0);
}
}, [visible, debouncedQuery, fetchGifs]);
// Handle ScrollView layout to dynamically calculate column width
const handleLayout = (event) => {
const { width } = event.nativeEvent.layout;
setFlatListWidth(width);
};
const renderMasonryColumns = () => {
if (!flatListWidth)
return null;
const padding = 16; // Total horizontal padding
const columnSpacing = 8; // Spacing between columns
const columnWidth = (flatListWidth - padding - columnSpacing) / 2; // Two columns
// Split data into two columns
const columns = [[], []];
gifs.forEach((gif, index) => {
columns[index % 2].push(gif); // Alternately add to each column
});
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: {
flexDirection: "row",
justifyContent: "space-between",
paddingHorizontal: 4,
}, children: columns.map((column, colIndex) => ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: { flex: 1, marginHorizontal: 4 }, children: column.map((item) => {
const aspectRatio = parseInt(item.images.fixed_width.height) /
parseInt(item.images.fixed_width.width);
const imageStyle = {
width: columnWidth,
height: columnWidth * aspectRatio,
borderRadius: 4,
};
// Build the props based on which Image component is being used.
// For expo-image, we assume it accepts a string for its "source"
// and additional props like "contentFit" and "transition".
// For React Native's Image, we wrap the URL in an object with "uri".
const imageProps = isExpo
? {
source: item.images.fixed_width.webp, // expo-image accepts a string
style: imageStyle,
contentFit: "cover",
transition: 500,
}
: {
source: { uri: item.images.fixed_width.webp }, // React Native expects an object with a "uri" property
style: imageStyle,
};
return ((0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: { marginBottom: 8 }, onPress: () => {
react_native_1.Keyboard.dismiss(); // Dismiss the keyboard
onSelectGif({
id: item.id,
url: item.url,
aspectRatio,
gifUrl: item.images.fixed_width.webp,
gifPreviewUrl: item.images.preview_gif.webp,
altText: item.title,
});
}, children: (0, jsx_runtime_1.jsx)(ImageComponent, { ...imageProps }) }, item.id));
}) }, colIndex))) }));
};
return ((0, jsx_runtime_1.jsx)(react_native_1.TouchableWithoutFeedback, { onPress: react_native_1.Keyboard.dismiss, accessible: false, children: (0, jsx_runtime_1.jsxs)(react_native_1.Animated.View, { style: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "white",
zIndex: 999,
opacity: visible ? 1 : 0,
pointerEvents: visible ? "auto" : "none",
}, children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: {
flexDirection: "row",
padding: 8,
alignItems: "stretch",
gap: 8,
}, children: [(0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: {
backgroundColor: "#e5e7eb",
aspectRatio: 1, // Ensures width equals height
alignItems: "center",
justifyContent: "center",
borderRadius: 8, // Keeps it rounded
}, onPress: onClickBack, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: { color: "#888", fontSize: 22, lineHeight: 22 }, children: "\u2190" }) }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: {
flex: 1,
flexDirection: "row",
backgroundColor: "#e5e7eb",
borderRadius: 8,
paddingHorizontal: 16,
alignItems: "center",
gap: 12,
}, children: [(0, jsx_runtime_1.jsx)(icons_1.MagnifyingGlassIcon, { width: 16, color: "#888" }), (0, jsx_runtime_1.jsx)(react_native_1.TextInput, { style: {
flex: 1,
paddingVertical: 12,
fontSize: 15,
}, placeholder: "Search GIPHY", onChangeText: (value) => setQuery(value), value: query })] })] }), loading && gifs.length === 0 ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: { textAlign: "center", marginTop: 16 }, children: "Loading..." })) : ((0, jsx_runtime_1.jsxs)(react_native_gesture_handler_1.ScrollView, { onLayout: handleLayout, onScroll: ({ nativeEvent }) => {
const { layoutMeasurement, contentOffset, contentSize } = nativeEvent;
const currentScroll = contentOffset.y + layoutMeasurement.height;
const threshold = contentSize.height * 0.8; // 80% scroll
if (currentScroll >= threshold) {
fetchMoreGifs();
}
}, keyboardShouldPersistTaps: "handled" // Important to allow tapping through the keyboard
, scrollEventThrottle: 16, children: [renderMasonryColumns(), loadingMore && ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: { textAlign: "center", marginVertical: 16 }, children: "Loading more..." }))] }))] }) }));
}
//# sourceMappingURL=GiphyContainer.js.map
;