tiefen_space_app/screeens/StreamerProfile/index.jsx

916 lines
31 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,
Dimensions,
TouchableOpacity,
TouchableWithoutFeedback,
Modal,
Text,
Alert,
ScrollView,
Platform,
Image as NativeImage,
} from "react-native";
import React, { useState, useEffect } from "react";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useTailwind } from "tailwind-rn";
import { Image } from "expo-image";
import Swiper from "react-native-swiper";
import { Divider, Icon } from "@rneui/themed";
import VideoModal from "../../components/VideoModal";
import { useHeaderHeight } from "@react-navigation/elements";
import * as Clipboard from "expo-clipboard";
import * as Linking from "expo-linking";
import GetWechatModal from "../../components/GetWechatModal";
import SubmitWechatModal from "../../components/SubmitWechatModal";
import Toast from "react-native-toast-message";
import baseRequest from "../../utils/baseRequest";
import { follow, unfollow, checkRelation, block } from "../../utils/relation";
import { get } from "../../utils/storeInfo";
import StreamerProfileSkeleton from "./skeleton";
import { generateSignature } from "../../utils/crypto";
import Svg, { Path } from "react-native-svg";
import { useImageViewer } from "../../context/ImageViewProvider";
const blurhash = "LcKUTa%gOYWBYRt6xuoJo~s8V@fk";
export default function StreamerProfile({ navigation, route }) {
const screenWidth = Dimensions.get("window").width;
const tailwind = useTailwind();
const insets = useSafeAreaInsets();
const headerHeight = useHeaderHeight();
//设置header右侧按钮功能
useEffect(() => {
navigation.setOptions({
headerRight: () => (
<Icon
type="ionicon"
name="ellipsis-vertical"
size={24}
color="white"
onPress={() => setNavModal(true)}
/>
),
});
}, []);
//动态是否需要blur
const [blur, setBlur] = useState(true);
const checkBlur = async () => {
const account = await get("account");
const role = account.role;
const isVip = account.is_a_member;
if (role !== 0 || isVip === 1) {
return setBlur(false);
}
return setBlur(true);
};
//页面数据
const [data, setData] = useState({});
const [postsData, setPostsData] = useState([]);
const [spaceData, setSpaceData] = useState();
const [isLoading, setIsloading] = useState(true);
useEffect(() => {
const getData = async () => {
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
try {
const base = await baseRequest();
const signature = await generateSignature({
mid: route.params.mid,
...base,
});
//获取主播数据
const detailResponse = await fetch(
`${apiUrl}/api/streamer/list_ext_by_mid?signature=${signature}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
mid: route.params.mid,
...base,
}),
}
);
const detailData = await detailResponse.json();
if (detailData.ret === -1) {
Toast.show({
type: "error",
text1: detailData.msg,
topOffset: 60,
});
return;
}
//获取当前所有平台
const signature2 = await generateSignature({
...base,
});
const allPlatformsResponse = await fetch(
`${apiUrl}/api/platform/list?signature=${signature2}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
...base,
}),
}
);
const allPlatformsData = await allPlatformsResponse.json();
if (allPlatformsData.ret === -1) {
Toast.show({
type: "error",
text1: allPlatformsData.msg,
topOffset: 60,
});
return;
}
//获取主播当前所有平台
const signature3 = await generateSignature({
mid: route.params.mid,
...base,
});
const streamerPlatformResponse = await fetch(
`${apiUrl}/api/streamer_link/list_by_mid?signature=${signature3}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
mid: route.params.mid,
...base,
}),
}
);
const streamerPlatformData = await streamerPlatformResponse.json();
if (streamerPlatformData.ret === -1) {
Toast.show({
type: "error",
text1: streamerPlatformData.msg,
topOffset: 60,
});
return;
}
const platformsWithIcon = streamerPlatformData.data.list.map((item) => {
return {
...item,
link_icon: allPlatformsData.data[item.link_no]?.icon,
};
});
setData({
...detailData.data.streamer_ext,
wechat_lock_type: detailData.data.wechat_lock_type,
platforms: platformsWithIcon,
});
setIsloading(false);
} catch (error) {
console.error(error);
}
};
const getPostsData = async () => {
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
try {
const base = await baseRequest();
const signature = await generateSignature({
mid: route.params.mid,
ct_upper_bound: Math.floor(new Date().getTime() / 1000),
offset: 0,
limit: 100,
...base,
});
const _response = await fetch(
`${apiUrl}/api/moment/list_by_mid?signature=${signature}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
mid: route.params.mid,
ct_upper_bound: Math.floor(new Date().getTime() / 1000),
offset: 0,
limit: 100,
...base,
}),
}
);
const _data = await _response.json();
if (_data.ret === -1) {
Toast.show({
type: "error",
text1: _data.msg,
topOffset: 60,
});
return;
}
setPostsData(_data.data.list);
} catch (error) {
console.error(error);
}
};
const getSpaceData = async () => {
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
try {
const base = await baseRequest();
const signature = await generateSignature({
mid: route.params.mid,
...base,
});
const _response = await fetch(
`${apiUrl}/api/zone/list_by_mid?signature=${signature}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
mid: route.params.mid,
...base,
}),
}
);
const _data = await _response.json();
if (_data.ret === -1) {
Toast.show({
type: "error",
text1: _data.msg,
topOffset: 60,
});
return;
}
setSpaceData(_data.data.list[0]);
} catch (error) {
console.error(error);
}
};
getData();
checkBlur();
getPostsData();
getSpaceData();
}, []);
//媒体展示组件
const MySwiper = () => {
//控制视频modal可见性
const [videoVisible, setVideoVisible] = useState(false);
const { showImageViewer } = useImageViewer();
const images = data?.album?.images?.map((image) => image?.urls[0]);
const imagesForImageViewer = images?.map((url) => ({ url }));
return (
<>
<Swiper
autoplay
width={screenWidth}
height={screenWidth}
activeDotStyle={tailwind("bg-[#FF669E]")}
>
{data?.album !== undefined &&
data?.cover !== undefined &&
[...data?.cover?.images, ...data?.album?.images].map(
(item, index) => (
<View key={index}>
{index === 0 ? (
<TouchableOpacity
activeOpacity={1}
style={tailwind(
"w-full h-full flex items-center justify-center"
)}
onPress={() => setVideoVisible(true)}
>
<Image
source={item?.urls[0]}
contentFit="cover"
transition={1000}
cachePolicy="disk"
style={tailwind("w-full h-full")}
/>
<View
style={tailwind(
"absolute flex items-center justify-center"
)}
>
<Svg viewBox="0 0 1024 1024" width="50" height="50">
<Path
d="M512 512m-512 0a44 44 0 1 0 1024 0 44 44 0 1 0-1024 0Z"
fill="#000000"
opacity="0.5"
></Path>
<Path
d="M746.327273 534.035416 418.181818 723.708144C401.890909 733.017235 380.945455 721.380871 380.945455 701.599053L380.945455 322.253598C380.945455 303.635416 401.890909 290.835416 418.181818 300.144507L746.327273 489.817235C763.781818 500.289962 763.781818 523.562689 746.327273 534.035416L746.327273 534.035416Z"
fill="#FFFFFF"
></Path>
</Svg>
</View>
</TouchableOpacity>
) : (
<TouchableWithoutFeedback
onPress={() => {
showImageViewer({
imageUrls: imagesForImageViewer,
index: index - 1,
});
}}
>
<Image
source={item?.urls[0]}
contentFit="cover"
transition={1000}
cachePolicy="disk"
style={tailwind("w-full h-full")}
/>
</TouchableWithoutFeedback>
)}
</View>
)
)}
</Swiper>
{/* 展示视频的modal */}
<VideoModal
visible={videoVisible}
setVisible={setVideoVisible}
url={data?.shorts?.videos[0]?.urls[0]}
/>
</>
);
};
//拉黑、举报组件
const [navModal, setNavModal] = useState(false);
const NavbarRightModal = () => {
const handleFeedback = () => {
navigation.navigate("MessageDetail", { mid: 1 });
setNavModal(false);
};
const handleBlock = async () => {
const account = await get("account");
const subMid = account.mid;
const objMid = route.params.mid;
await block(subMid, objMid);
setNavModal(false);
};
return (
<Modal
visible={navModal}
transparent={true}
statusBarTranslucent
animationType="fade"
>
<TouchableWithoutFeedback onPress={() => setNavModal(false)}>
<View
style={{
backgroundColor: "rgba(0,0,0,0.3)",
...tailwind("flex-1"),
}}
>
<View
style={{
...tailwind("items-center absolute bg-white rounded-lg p-2"),
top: headerHeight,
right: 24,
}}
>
<TouchableOpacity
onPress={handleBlock}
style={tailwind("flex-row items-center px-4")}
>
<Icon
type="ionicon"
name="remove-circle"
color="#f87171"
size={24}
/>
<Text style={tailwind("text-base")}>拉黑</Text>
</TouchableOpacity>
<Divider style={tailwind("w-full my-2")} />
<TouchableOpacity
onPress={handleFeedback}
style={tailwind("flex-row items-center px-4")}
>
<Icon type="ionicon" name="warning" color="#60a5fa" size={24} />
<Text style={tailwind("text-base")}>举报</Text>
</TouchableOpacity>
</View>
</View>
</TouchableWithoutFeedback>
</Modal>
);
};
// 平台列表
const PlatformList = ({ item }) => {
return (
<View
style={tailwind(
"flex-row justify-between items-center h-12 mt-2 p-2 rounded-xl border-2 border-[#2c2b2f]"
)}
>
<View style={tailwind("flex-row items-center w-1/3")}>
<Image
source={item?.link_icon?.images[0]?.urls[0]}
style={{ aspectRatio: "1/1", ...tailwind("w-8") }}
/>
<Text style={tailwind("text-sm text-white ml-1")}>
{item?.link_name}
</Text>
<Text
numberOfLines={1}
ellipsizeMode="tail"
style={tailwind("text-sm text-white ml-1")}
>
{item?.nickname}
</Text>
</View>
<View style={tailwind("flex-row")}>
<TouchableOpacity
onPress={async () => {
await Clipboard.setStringAsync(item.url);
Alert.alert(null, "复制成功");
}}
style={tailwind("flex-row items-center")}
>
<NativeImage source={require("../../assets/icon/24DP/copy.png")} />
<Text style={tailwind("text-xs text-white")}>复制</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => Linking.openURL(item.url)}
style={tailwind("flex-row items-center ml-4")}
>
<NativeImage source={require("../../assets/icon/24DP/goto.png")} />
<Text style={tailwind("text-xs text-white")}>前往</Text>
</TouchableOpacity>
</View>
</View>
);
};
//点击关注按钮
const [isFollowed, setIsFollowed] = useState(false);
const handleFollow = async () => {
const account = await get("account");
if (isFollowed) {
await unfollow(account.mid, data?.mid);
setIsFollowed(!isFollowed);
} else {
await follow(account.mid, data?.mid);
setIsFollowed(!isFollowed);
}
};
//加载页面时检查关注状态
useEffect(() => {
const getRelationData = async () => {
const account = await get("account");
const subMid = account.mid;
const objMid = route.params.mid;
const temIsFollowed = await checkRelation(subMid, objMid, 0);
setIsFollowed(temIsFollowed);
};
getRelationData();
}, []);
//点击私聊
const handleSendMessage = () => {
navigation.navigate("MessageDetail", {
mid: route.params.mid,
});
};
//点击查看微信按钮
const [isAddWechatModalVisible, setIsAddWechatModalVisible] = useState(false);
//加载初始骨架屏
if (isLoading) return <StreamerProfileSkeleton />;
return (
<View style={{ ...tailwind("flex-1"), paddingBottom: insets.bottom }}>
<ScrollView
style={{
paddingBottom: insets.bottom,
paddingLeft: insets.left,
paddingRight: insets.right,
...tailwind("flex-1"),
}}
>
{/* 媒体展示、在线状态 */}
<MySwiper />
<View style={tailwind("flex flex-1 mt-4")}>
<View style={tailwind("flex-1 px-4")}>
{/* 昵称、认证 */}
<View style={tailwind("flex flex-row items-center")}>
<Text
style={tailwind("text-2xl text-white font-medium mr-1")}
numberOfLines={1}
ellipsizeMode="tail"
>
{data?.name}
</Text>
<NativeImage
source={require("../../assets/icon/others/verification.png")}
/>
</View>
{/* tag*/}
<View style={tailwind("flex-row my-2.5")}>
{data?.tag !== undefined &&
data?.tag.map((item, index) => {
if (index > 2) return;
return (
<View
key={index}
style={{
...tailwind("py-1 px-2 rounded-md mr-2"),
backgroundColor: "#FF669E",
}}
>
<Text style={tailwind("text-xs text-white")}>{item}</Text>
</View>
);
})}
</View>
{/* 标签 */}
<View style={tailwind("flex-row flex-wrap pb-1")}>
<View
style={tailwind(
"flex-row items-center py-0.5 px-2 mr-2 my-1 bg-[#FFFFFF1A] rounded-full"
)}
>
<NativeImage
source={require("../../assets/icon/12DP/ID.png")}
/>
<Text style={tailwind("text-white text-xs font-medium ml-0.5")}>
{data?.user_id}
</Text>
</View>
<View
style={tailwind(
"flex-row items-center py-0.5 px-2 mr-2 my-1 bg-[#FFFFFF1A] rounded-full"
)}
>
<NativeImage
source={require("../../assets/icon/12DP/fan.png")}
/>
<Text style={tailwind("text-white text-xs font-medium ml-0.5")}>
{`全网粉丝 : ${data?.fans}`}
</Text>
</View>
{data?.age && (
<View
style={tailwind(
"flex-row items-center py-0.5 px-2 mr-2 my-1 bg-[#FFFFFF1A] rounded-full"
)}
>
{data?.gender === 1 ? (
<NativeImage
source={require("../../assets/icon/12DP/female.png")}
/>
) : (
<NativeImage
source={require("../../assets/icon/12DP/male.png")}
/>
)}
<Text
style={tailwind("text-white text-xs font-medium ml-0.5")}
>
{data?.age}
</Text>
</View>
)}
{data?.height && (
<View
style={tailwind(
"flex-row items-center py-0.5 px-2 mr-2 my-1 bg-[#FFFFFF1A] rounded-full"
)}
>
<NativeImage
source={require("../../assets/icon/12DP/height.png")}
/>
<Text
style={tailwind("text-white text-xs font-medium ml-0.5")}
>
{`${data?.height}cm`}
</Text>
</View>
)}
{data?.weight && (
<View
style={tailwind(
"flex-row items-center py-0.5 px-2 mr-2 my-1 bg-[#FFFFFF1A] rounded-full"
)}
>
<NativeImage
source={require("../../assets/icon/12DP/weight.png")}
/>
<Text
style={tailwind("text-white text-xs font-medium ml-0.5")}
>
{`${data?.weight}kg`}
</Text>
</View>
)}
{data?.constellation && (
<View
style={tailwind(
"flex-row items-center py-0.5 px-2 mr-2 my-1 bg-[#FFFFFF1A] rounded-full"
)}
>
<NativeImage
source={require("../../assets/icon/12DP/constellation.png")}
/>
<Text
style={tailwind("text-white text-xs font-medium ml-0.5")}
>
{data?.constellation}
</Text>
</View>
)}
{data?.city && (
<View
style={tailwind(
"flex-row items-center py-0.5 px-2 mr-2 my-1 bg-[#FFFFFF1A] rounded-full"
)}
>
<NativeImage
source={require("../../assets/icon/12DP/location.png")}
/>
<Text
style={tailwind("text-white text-xs font-medium ml-0.5")}
>
{data?.city}
</Text>
</View>
)}
</View>
{/* 个性签名 */}
<Text style={tailwind("text-sm text-[#FFFFFF80] pb-4")}>
个性签名 |{" "}
<Text style={tailwind("text-sm text-white")}>{data?.bio}</Text>
</Text>
<View
style={tailwind("h-[3px] rounded-full bg-[#FFFFFF26]")}
></View>
{/* 空间动态 */}
{spaceData && spaceData?.previews?.images?.length > 0 && (
<View>
<View style={tailwind("my-4")}>
<TouchableOpacity
activeOpacity={1}
onPress={
spaceData?.visitor_role === 4
? () =>
navigation.navigate("SpaceIntroduce", {
mid: route.params.mid,
})
: () =>
navigation.navigate("StreamerSpace", {
mid: route.params.mid,
})
}
style={tailwind(
"flex flex-row justify-between items-center"
)}
>
<Text
style={tailwind(
"text-base font-semibold text-white mb-2"
)}
>
空间动态
</Text>
<View style={tailwind("flex flex-row items-center")}>
<Text style={tailwind("text-sm text-[#FFFFFFB2]")}>
查看{spaceData?.zone_moment_count}
</Text>
<NativeImage
source={require("../../assets/icon/32DP/smalllink.png")}
/>
</View>
</TouchableOpacity>
<ScrollView horizontal>
<TouchableOpacity
activeOpacity={1}
onPress={
spaceData?.visitor_role === 4
? () =>
navigation.navigate("SpaceIntroduce", {
mid: route.params.mid,
})
: () =>
navigation.navigate("StreamerSpace", {
mid: route.params.mid,
})
}
style={{
height: 100,
gap: 4,
...tailwind("flex flex-row"),
}}
>
{spaceData?.previews?.images?.map((item, index) => {
if (index > 3) return;
return (
<Image
key={index}
style={{
aspectRatio: "1",
...tailwind("h-full rounded"),
}}
blurRadius={
spaceData?.visitor_role === 4
? Platform.OS === "ios"
? 130
: 10
: 0
}
placeholder={blurhash}
source={item?.urls[0]}
contentFit="cover"
transition={100}
cachePolicy="disk"
/>
);
})}
</TouchableOpacity>
</ScrollView>
</View>
<View
style={tailwind("h-[3px] rounded-full bg-[#FFFFFF26]")}
></View>
</View>
)}
{/* 动态 */}
{!spaceData && postsData.length !== 0 && (
<View>
<View style={tailwind("my-4")}>
<Text
style={tailwind("text-base font-semibold text-white mb-2")}
>
动态{postsData.length}
</Text>
<TouchableOpacity
activeOpacity={1}
style={tailwind(
"flex flex-row justify-between items-center"
)}
onPress={() =>
navigation.navigate("StreamerPosts", {
mid: route.params.mid,
})
}
>
<View style={tailwind("flex flex-1 flex-row")}>
{postsData.map((item, index) => {
if (index > 3) return;
const mediaUrl =
item.media_component.images.length !== 0
? item.media_component.images[0].urls[0]
: item.media_component.videos[0].cover_urls[0];
return (
<View
key={index}
style={{
aspectRatio: "1",
...tailwind("basis-1/4 p-1"),
}}
>
<View style={tailwind("relative w-full h-full")}>
<Image
style={tailwind("w-full h-full rounded")}
blurRadius={
blur ? (Platform.OS === "ios" ? 130 : 10) : 0
}
placeholder={blurhash}
source={mediaUrl}
contentFit="cover"
transition={100}
cachePolicy="disk"
/>
{index === 3 && postsData.length > 4 && (
<View
style={{
backgroundColor: "rgba(0,0,0,0.5)",
...tailwind(
"w-full h-full rounded absolute top-0 left-0 flex justify-center items-center"
),
}}
>
<Text
style={tailwind(
"text-white text-2xl font-semibold"
)}
>
+{postsData.length - 4}
</Text>
</View>
)}
</View>
</View>
);
})}
</View>
<NativeImage
source={require("../../assets/icon/32DP/smalllink.png")}
/>
</TouchableOpacity>
</View>
<View
style={tailwind("h-[3px] rounded-full bg-[#FFFFFF26]")}
></View>
</View>
)}
{/* 平台 */}
<View style={tailwind("my-4")}>
<Text style={tailwind("text-base text-white font-semibold mb-2")}>
来这找我玩
</Text>
<View
style={tailwind(
"flex-row items-center p-2 h-12 rounded-xl border-2 border-[#2c2b2f]"
)}
>
<NativeImage
source={require("../../assets/images/platform_wechat.png")}
style={{ aspectRatio: "1/1", ...tailwind("w-8") }}
/>
<Text style={tailwind("text-sm text-white ml-1")}>微信</Text>
<Text
onPress={() => setIsAddWechatModalVisible(true)}
style={tailwind("text-sm text-[#3B69B8] ml-1")}
numberOfLines={1}
ellipsizeMode="tail"
>
点击查看
</Text>
</View>
{data?.platforms?.map((item) => (
<PlatformList key={item?.id} item={item} />
))}
</View>
</View>
</View>
</ScrollView>
{/* 关注、私聊、查看微信 */}
<View
style={{
borderTopColor: "#FFFFFF26",
...tailwind(
"flex-row py-2 px-4 h-[4.5rem] border-t items-center justify-between"
),
}}
>
<TouchableOpacity
onPress={handleFollow}
style={{
backgroundColor: "#FFFFFF1A",
...tailwind(
"flex-row items-center justify-center h-10 px-6 rounded-full mr-4"
),
}}
>
<Text style={tailwind("text-base text-white font-medium")}>
{isFollowed ? "已关注" : "关注"}
</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={handleSendMessage}
style={tailwind(
"flex-row bg-[#FFFFFF1A] items-center justify-center h-10 px-6 rounded-full mr-4"
)}
>
<Text style={tailwind("text-base text-white font-medium")}>私聊</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => setIsAddWechatModalVisible(true)}
style={tailwind(
"flex-row flex-1 bg-[#FF669E] items-center justify-center h-10 rounded-full"
)}
>
<Text style={tailwind("text-base text-white font-medium")}>
添加微信
</Text>
</TouchableOpacity>
</View>
{/* 拉黑、举报Modal */}
<NavbarRightModal />
{/* 查看微信Modal */}
{data?.wechat_lock_type === 0 ? (
<GetWechatModal
visible={isAddWechatModalVisible}
setVisible={setIsAddWechatModalVisible}
streamerMid={route.params.mid}
/>
) : (
<SubmitWechatModal
visible={isAddWechatModalVisible}
setVisible={setIsAddWechatModalVisible}
streamerMid={route.params.mid}
/>
)}
</View>
);
}