tiefen_space_app/screeens/Stream/FeedStream/index.jsx

244 lines
8.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
View,
RefreshControl,
ActivityIndicator,
Animated,
Easing,
TouchableOpacity,
} from "react-native";
import React, { useState, useRef, createContext, useEffect } from "react";
import StreamerCard from "../../../components/StreamerCard";
import Empty from "../../../components/Empty";
import { useTailwind } from "tailwind-rn";
import baseRequest from "../../../utils/baseRequest";
import Toast from "react-native-toast-message";
import { generateSignature } from "../../../utils/crypto";
import { FlashList } from "@shopify/flash-list";
import { Icon } from "@rneui/themed";
import { Svg, Path } from "react-native-svg";
export const FeedShowVideoContext = createContext("");
export default function FeedStream() {
//获取推荐的主播mid
const [recommMids, setRecommMids] = useState([]);
const getRecommMids = async () => {
if (!isEnd) return;
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
try {
const base = await baseRequest();
const signature = await generateSignature({
...base,
});
const response = await fetch(
`${apiUrl}/api/streamer/recomm_list?signature=${signature}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
...base,
}),
}
);
const recommData = await response.json();
if (recommData.ret === -1) {
Toast.show({
type: "error",
text1: recommData.msg,
topOffset: 60,
});
return;
}
setIsEnd(false);
setRecommMids(recommData.data.recomm_list);
} catch (error) {
console.error(error);
}
};
//查询的起始索引
const [startIndex, setStartIndex] = useState(0);
// 定义每次取多少个用户ID
const batchSize = 10;
//获取新数据
const [data, setData] = useState([]);
const [isEnd, setIsEnd] = useState(true);
const [isActivityIndicatorShow, setIsActivityIndicatorShow] = useState(true);
const [isFetching, setIsFetching] = useState(false);
const getData = async (type) => {
if (isFetching) return;
if (isEnd) return;
if (recommMids.length === 0) return;
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
const batchStreamerMids = recommMids.slice(
startIndex,
startIndex + batchSize
);
if (batchStreamerMids.length === 0) {
if (type !== "top") {
setIsActivityIndicatorShow(false);
return;
}
setIsActivityIndicatorShow(true);
setIsEnd(true);
setStartIndex(0);
return;
}
setIsFetching((prev) => true);
try {
const base = await baseRequest();
const signature = await generateSignature({
mids: batchStreamerMids,
...base,
});
const detailResponse = await fetch(
`${apiUrl}/api/streamer/list_ext_by_mids?signature=${signature}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
mids: batchStreamerMids,
...base,
}),
}
);
const detailData = await detailResponse.json();
if (detailData.ret === -1) {
Toast.show({
type: "error",
text1: detailData.msg,
topOffset: 60,
});
return;
}
const sortedData = detailData.data.list.sort((a, b) => b.fans - a.fans);
if (type === "top") {
setData((prev) => {
if (startIndex === 0) {
return sortedData;
} else return [...sortedData, ...prev];
});
} else {
setData((prev) => [...prev, ...sortedData]);
}
// 更新下一次查询的起始索引
setStartIndex(startIndex + batchSize);
setIsFetching((prev) => false);
} catch (error) {
console.error(error);
}
};
//获取推荐mid
useEffect(() => {
getRecommMids();
}, [isEnd]);
//当推荐的mid改变获取新数据
useEffect(() => {
getData("top");
}, [recommMids]);
const tailwind = useTailwind();
const renderItem = ({ item }) => <StreamerCard data={item} screen="feed" />;
const [refreshing, setRefreshing] = useState(false);
const [showVideo, setShowVideo] = useState([]);
const handleVideoShow = useRef(({ viewableItems }) => {
let visibleMid = viewableItems.map((item) => {
return item.item.mid;
});
setShowVideo(visibleMid);
}).current;
//刷新动画
const flatListRef = useRef(null);
const [rotateValue] = useState(new Animated.Value(0));
const startRotation = () => {
Animated.timing(rotateValue, {
toValue: 1,
duration: 500,
easing: Easing.linear,
useNativeDriver: true,
}).start(() => {
rotateValue.setValue(0);
});
};
const spin = rotateValue.interpolate({
inputRange: [0, 1],
outputRange: ["0deg", "360deg"],
});
//下拉刷新
const handleRefresh = async () => {
startRotation();
flatListRef.current.scrollToOffset({ animated: false, offset: 0 });
setRefreshing(true);
await getData("top");
setRefreshing(false);
};
return (
<FeedShowVideoContext.Provider value={showVideo}>
<View style={tailwind("flex-1")}>
<FlashList
ref={flatListRef}
data={data}
renderItem={renderItem}
estimatedItemSize={352}
initialNumToRender={10}
refreshControl={
<RefreshControl
colors={["#FF669E"]}
tintColor="white"
refreshing={refreshing}
onRefresh={() => handleRefresh()}
/>
}
onEndReached={() => getData("bottom")}
ListEmptyComponent={<Empty type="nodata" />}
ListFooterComponent={
<View>
{isActivityIndicatorShow && data.length !== 0 && (
<ActivityIndicator style={tailwind("my-4")} size="large" />
)}
</View>
}
viewabilityConfig={{
waitForInteraction: false,
itemVisiblePercentThreshold: 10,
}}
onViewableItemsChanged={handleVideoShow}
keyExtractor={(item) => item.mid}
/>
<TouchableOpacity
style={tailwind(
"absolute right-5 bottom-5 bg-[#13121F] p-1 rounded-full"
)}
onPress={handleRefresh}
>
<Animated.View style={{ transform: [{ rotate: spin }] }}>
<Svg viewBox="0 0 1024 1024" width="30" height="30">
<Path
d="M855.7 546.3c-16.6 0-30-13.5-30-30.1 0.1-72.8-25.1-143.9-71-200.4l-1-1.3c-31.5-38.5-72.1-69.2-117.3-88.9l-0.6-0.2-2.2-1c-6.8-2.9-14.3-5.6-22.9-8.4l-0.6-0.2-6.5-2.3c-7.1-2.1-14.7-3.9-20.6-5.3-1.2-0.3-2.3-0.5-3.5-0.8-2.1-0.5-4-1-5.6-1.3l-0.4-0.1c-6.8-1.4-14.1-2.5-21.7-3.3l-0.8-0.1-8.4-1.2c-73.7-7.1-147.4 12.6-207.8 55.6-13.5 9.6-32.2 6.5-41.8-7-9.6-13.5-6.5-32.2 7-41.8 72.3-51.5 160.8-75.1 249.1-66.4l1.3 0.2 8.7 1.2c9 1 17.8 2.4 26 4 3.1 0.6 6 1.3 8.6 1.9 1 0.2 1.9 0.5 2.9 0.7 7 1.6 16.2 3.8 25.2 6.5l1.2 0.4 6.9 2.4c10.2 3.4 19.3 6.7 27.7 10.3l0.8 0.4 2.4 1.1c54.1 23.6 102.4 60.4 139.9 106.3l1 1.4c54.4 67.1 84.2 151.5 84.1 237.9-0.1 16.4-13.6 29.8-30.1 29.8z"
fill="#ffffff"
></Path>
<Path
d="M872.5 661.6l78.9-114.5c9-13.1-0.3-30.9-16.3-30.9H777.4c-15.9 0-25.3 17.8-16.3 30.9L840 661.6c7.9 11.4 24.7 11.4 32.5 0zM513.3 884.1c-12 0-24-0.6-36-1.8l-1.3-0.2-8.6-1.2c-9-1-17.8-2.4-26-4-3.1-0.6-6-1.3-8.6-1.9-1-0.2-1.9-0.5-2.9-0.7-7-1.6-16.2-3.8-25.2-6.5l-1.2-0.4-6.9-2.4c-10.2-3.4-19.3-6.7-27.7-10.3l-0.8-0.4-2.4-1.1c-54.1-23.6-102.4-60.4-139.9-106.3l-1-1.4c-54.4-67.1-84.2-151.5-84.1-237.9 0-16.6 13.5-29.9 30-29.9h0.1c16.6 0 30 13.5 29.9 30.1-0.1 72.8 25.1 143.9 71 200.4l1 1.3c31.5 38.5 72.1 69.2 117.3 88.9l2.8 1.3c6.8 2.9 14.3 5.6 22.9 8.4l0.6 0.2 6.5 2.3c7.1 2.1 14.7 3.9 20.6 5.3 1.2 0.3 2.3 0.5 3.5 0.8 2.1 0.5 4 1 5.6 1.3l0.4 0.1c6.8 1.3 14.1 2.5 21.7 3.3l0.8 0.1 8.4 1.2c73.7 7.1 147.4-12.6 207.8-55.6 13.5-9.6 32.2-6.5 41.8 7 9.6 13.5 6.5 32.2-7 41.8-62.5 44.5-137 68.2-213.1 68.2z"
fill="#ffffff"
></Path>
<Path
d="M153.8 362.4L74.9 476.9c-9 13.1 0.3 30.9 16.3 30.9H249c15.9 0 25.3-17.8 16.3-30.9l-78.9-114.5c-7.9-11.4-24.7-11.4-32.6 0z"
fill="#ffffff"
></Path>
</Svg>
</Animated.View>
</TouchableOpacity>
</View>
</FeedShowVideoContext.Provider>
);
}