tiefen_space_app/components/SpacePost/index.jsx

953 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,
Text,
TouchableOpacity,
Modal,
TouchableWithoutFeedback,
Platform,
Image as NativeImage,
} from "react-native";
import React, { useEffect, useState } from "react";
import { useTailwind } from "tailwind-rn";
import { Image } from "expo-image";
import VideoModal from "../VideoModal";
import { useNavigation } from "@react-navigation/native";
import formatTimestamp from "../../utils/formatTimestamp";
import { block } from "../../utils/relation";
import baseRequest from "../../utils/baseRequest";
import { generateSignature } from "../../utils/crypto";
import Toast from "react-native-toast-message";
import { get } from "../../utils/storeInfo";
import { Icon } from "@rneui/themed";
import ParsedText from "react-native-parsed-text";
import * as Linking from "expo-linking";
import { useImageViewer } from "../../context/ImageViewProvider";
//todo:完善视频逻辑;完善图片模糊逻辑
const blurhash = "LcKUTa%gOYWBYRt6xuoJo~s8V@fk";
export default function SpacePost({ data }) {
const tailwind = useTailwind();
const navigation = useNavigation();
const [like, setLike] = useState(
data?.is_zone_moment_thumbed_up === 1 ? true : false
);
const [showVideo, setShowVideo] = useState(false);
//判断是否是发帖人
const [isCreator, setIsCreator] = useState(false);
useEffect(() => {
const checkAuth = async () => {
const account = await get("account");
if (account.mid === data.mid) setIsCreator(true);
};
checkAuth();
}, []);
//点赞和取消点赞功能
const thumbsUp = async (id, times = 1) => {
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
try {
const base = await baseRequest();
const signature = await generateSignature({
zone_moment_id: id,
times: times,
...base,
});
const _response = await fetch(
`${apiUrl}/api/zone_moment/thumbs_up?signature=${signature}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
zone_moment_id: id,
times: times,
...base,
}),
}
);
const _data = await _response.json();
if (_data.ret === -1) {
Toast.show({
type: "error",
text1: _data.msg,
topOffset: 60,
});
return;
}
} catch (error) {
console.error(error);
}
};
const handleLike = () => {
setLike(!like);
if (like) {
thumbsUp(data.id, -1);
return;
}
thumbsUp(data.id, 1);
};
//帖子操作功能
const [operationModalVisible, setOperationModalVisible] = useState(false);
//点击文本中的网址
const handleURLPress = (url) => {
Linking.openURL(url);
};
//网址样式
const renderLink = (matchingString, matches) => {
return (
<Text
style={tailwind("text-[#3B69B8]")}
onPress={() => handleURLPress(matchingString)}
>
#网页链接
</Text>
);
};
return (
<View style={tailwind("flex flex-col")}>
{data?.is_headed === 1 && (
<NativeImage
style={tailwind("absolute left-0 top-0")}
source={require("../../assets/icon/others/top_post.png")}
/>
)}
<View
style={{
...tailwind("flex"),
paddingTop: 20,
paddingLeft: 14,
paddingBottom: 6,
}}
>
<View style={tailwind("flex flex-row")}>
<TouchableWithoutFeedback
onPress={() =>
navigation.navigate("StreamerProfile", { mid: data?.mid })
}
>
<Image
style={tailwind("w-9 h-9 rounded-full")}
source={data?.streamer_ext?.avatar?.images[0]?.urls[0]}
placeholder={blurhash}
contentFit="cover"
transition={100}
cachePolicy="disk"
/>
</TouchableWithoutFeedback>
<View
style={tailwind("flex flex-col flex-1 justify-around ml-2 pr-4")}
>
<View
style={tailwind("flex flex-row items-center justify-between")}
>
<Text style={tailwind("text-base font-medium text-white")}>
{data?.streamer_ext?.name}
</Text>
</View>
{(data?.status === 0 || data?.status === 1) && (
<View style={tailwind("flex flex-col items-start mt-2")}>
<View
style={{
backgroundColor: "#3B69B8",
...tailwind("py-1 px-2 rounded"),
}}
>
<Text style={tailwind("text-white text-sm")}>审核中</Text>
</View>
</View>
)}
{data?.status === 3 && (
<TouchableOpacity
onPress={() =>
navigation.navigate("EditSpacePost", { data: data })
}
style={tailwind("flex flex-col items-start mt-2")}
>
<View style={tailwind("py-1 px-2 bg-[#F53030] rounded")}>
<Text style={tailwind("text-white text-sm")}>
审核未通过
<Text style={{ textDecorationLine: "underline" }}>
重新编辑
</Text>
</Text>
</View>
</TouchableOpacity>
)}
<ParsedText
style={tailwind("text-base text-white font-medium my-2")}
parse={[
{
type: "url",
renderText: renderLink,
},
]}
numberOfLines={
data.is_zone_moment_unlocked === 0 ? data.text_visible_range : 0
}
ellipsizeMode="tail"
>
{data?.text}
</ParsedText>
{/* 媒体展示 */}
<View style={tailwind("pr-10 mb-1")}>
{data.media_component.video_ids?.length === 0 ||
data.media_component.video_ids === null ? (
<ImageDisplay
data={data}
isCreator={isCreator}
isUnlocked={data.is_zone_moment_unlocked}
visibleRange={data.media_visible_range}
mediaAmount={data.media_amount}
media={data.media_component?.images}
/>
) : (
<TouchableOpacity
activeOpacity={1}
onPress={
data.is_zone_moment_unlocked === 1
? () => {
setShowVideo(true);
}
: () =>
navigation.navigate("WebWithoutHeader", {
uri:
process.env.EXPO_PUBLIC_WEB_URL +
"/zone/pay/" +
data?.zid +
"/h5_zone_moment/" +
data?.id,
})
}
>
<View>
<PosterDisplay
isUnlocked={data.is_zone_moment_unlocked}
blurCover={data.is_blurring_cover}
media={data.media_component?.videos[0]}
/>
<VideoModal
visible={showVideo}
setVisible={setShowVideo}
url={data.media_component.videos[0].urls[0]}
/>
</View>
</TouchableOpacity>
)}
</View>
{/* 用户未解锁时展示 */}
{data.c_type === 1 && data.is_zone_moment_unlocked === 0 && (
<TouchableOpacity
activeOpacity={1}
onPress={() =>
navigation.navigate("WebWithoutHeader", {
uri:
process.env.EXPO_PUBLIC_WEB_URL +
"/zone/pay/" +
data?.zid +
"/h5_zone_moment/" +
data?.id,
})
}
style={{
backgroundColor:
data.is_ironfan_visible === 1 ? "#301024" : "#331F0B",
...tailwind("flex flex-col py-2.5 px-3 mr-10 rounded-lg"),
}}
>
<View
style={tailwind("flex flex-row justify-between items-center")}
>
<View style={tailwind("flex flex-row items-center")}>
<NativeImage
source={
data.is_ironfan_visible === 1
? require("../../assets/icon/others/money_pink.png")
: require("../../assets/icon/others/money_gold.png")
}
/>
<Text
style={{
color:
data.is_ironfan_visible === 1 ? "#FF669E" : "#FFD685",
...tailwind("text-base font-semibold ml-1"),
}}
>
{data.price / 100}
</Text>
<Text
style={{
color:
data.is_ironfan_visible === 1 ? "#FF669E" : "#FFD685",
...tailwind("text-sm font-medium"),
}}
>
</Text>
</View>
<View style={tailwind("flex flex-row items-center")}>
<Text
style={{
color:
data.is_ironfan_visible === 1 ? "#FF669E" : "#FFD685",
...tailwind("text-sm font-medium"),
}}
>
{data.is_ironfan_visible === 1
? "铁粉免费查看"
: data.is_superfanship_enabled === 1
? "超粉免费查看"
: "付费解锁"}
</Text>
<Icon
type="ionicon"
name="chevron-forward"
size={16}
color={
data.is_ironfan_visible === 1 ? "#FF669E" : "#FFD685"
}
/>
</View>
</View>
{data.is_ironfan_visible === 1 && (
<Text style={tailwind("text-xs text-[#FFFFFF40] mt-1")}>
空间内任意消费满{data.ironfanship_price / 100}即可成为铁粉
</Text>
)}
</TouchableOpacity>
)}
{/* 用户已解锁时展示 */}
{data.c_type === 1 &&
data.is_zone_moment_unlocked === 1 &&
!isCreator && (
<TouchableOpacity
activeOpacity={1}
style={{
backgroundColor:
data.is_ironfan_visible === 1 ? "#301024" : "#331F0B",
...tailwind("flex flex-col py-2.5 px-3 mr-10 rounded-lg"),
}}
>
<View
style={tailwind(
"flex flex-row justify-between items-center"
)}
>
<View style={tailwind("flex flex-row items-center")}>
<NativeImage
source={
data.is_ironfan_visible === 1
? require("../../assets/icon/others/money_pink.png")
: require("../../assets/icon/others/money_gold.png")
}
/>
<Text
style={{
color:
data.is_ironfan_visible === 1
? "#FF669E"
: "#FFD685",
...tailwind("text-base font-semibold ml-1"),
}}
>
{data.price / 100}
</Text>
<Text
style={{
color:
data.is_ironfan_visible === 1
? "#FF669E"
: "#FFD685",
...tailwind("text-sm font-medium"),
}}
>
</Text>
</View>
<View style={tailwind("flex flex-row items-center")}>
<Text
style={{
color:
data.is_ironfan_visible === 1
? "#FF669E"
: "#FFD685",
...tailwind("text-sm font-medium"),
}}
>
{data.is_ironfanship_unlocked === 1 &&
data.is_ironfan_visible === 1 &&
"已使用铁粉特权解锁"}
{data.is_superfanship_unlocked === 1 &&
data.is_ironfan_visible === 0 &&
"已使用超粉特权解锁"}
{data.is_superfanship_unlocked === 0 &&
data.is_ironfanship_unlocked === 0 &&
"已付费解锁"}
</Text>
<Icon
type="ionicon"
name="chevron-forward"
size={16}
color={
data.is_ironfan_visible === 1 ? "#FF669E" : "#FFD685"
}
/>
</View>
</View>
</TouchableOpacity>
)}
{/* 仅对发帖人展示 */}
{data.c_type === 1 && isCreator && (
<TouchableOpacity
activeOpacity={1}
onPress={() =>
navigation.navigate("WebWithoutHeader", {
uri:
process.env.EXPO_PUBLIC_WEB_URL +
"/zone/pay/" +
data?.zid +
"/h5_zone_moment/" +
data?.id,
})
}
style={{
backgroundColor:
data.is_ironfan_visible === 1 ? "#301024" : "#331F0B",
...tailwind("flex flex-col py-2.5 px-3 mr-10 rounded-lg"),
}}
>
<View
style={tailwind("flex flex-row justify-between items-center")}
>
<View style={tailwind("flex flex-row items-center")}>
<NativeImage
source={
data.is_ironfan_visible === 1
? require("../../assets/icon/others/money_pink.png")
: require("../../assets/icon/others/money_gold.png")
}
/>
<Text
style={{
color:
data.is_ironfan_visible === 1 ? "#FF669E" : "#FFD685",
...tailwind("text-base font-semibold ml-1"),
}}
>
{data.price / 100}
</Text>
<Text
style={{
color:
data.is_ironfan_visible === 1 ? "#FF669E" : "#FFD685",
...tailwind("text-sm font-medium"),
}}
>
</Text>
</View>
<View style={tailwind("flex flex-row items-center")}>
<Text
style={{
color:
data.is_ironfan_visible === 1 ? "#FF669E" : "#FFD685",
...tailwind("text-sm font-medium"),
}}
>
{data.buyer_cnt}人购买
</Text>
<Icon
type="ionicon"
name="chevron-forward"
size={16}
color={
data.is_ironfan_visible === 1 ? "#FF669E" : "#FFD685"
}
/>
</View>
</View>
{data.is_ironfan_visible === 1 && (
<Text style={tailwind("text-xs text-[#FFFFFF40] mt-1")}>
空间内任意消费满{data.ironfanship_price / 100}即可成为铁粉
</Text>
)}
</TouchableOpacity>
)}
<View
style={tailwind(
"flex flex-row items-center justify-between h-8 mt-2"
)}
>
<Text style={tailwind("text-sm font-medium text-[#FFFFFF80]")}>
{formatTimestamp(data?.ct)}
</Text>
<View style={tailwind("flex flex-row items-center")}>
<TouchableOpacity
style={tailwind("flex flex-row items-center")}
onPress={handleLike}
>
{like ? (
<NativeImage
source={require("../../assets/icon/others/thumbup.png")}
/>
) : (
<NativeImage
source={require("../../assets/icon/others/notthumbup.png")}
/>
)}
<Text
style={{
color: like ? "#FF669E" : "#FFFFFF80",
...tailwind("text-xs"),
}}
>
{like ? "已赞" : "点赞"}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={tailwind("ml-2")}
onPress={() =>
setOperationModalVisible(!operationModalVisible)
}
>
<NativeImage
source={require("../../assets/icon/others/gray....png")}
/>
<OperationModal
visible={operationModalVisible}
setVisible={setOperationModalVisible}
data={data}
/>
</TouchableOpacity>
</View>
</View>
</View>
</View>
</View>
<View style={tailwind("h-[3px] rounded-full mx-4 bg-[#FFFFFF26]")}></View>
</View>
);
}
//媒体为图片时展示内容的组件
function ImageDisplay({
data,
isCreator,
isUnlocked,
visibleRange,
mediaAmount,
media,
}) {
const tailwind = useTailwind();
const navigation = useNavigation();
//如果图片数量与服务端图片数量不一致则填充第一张图片url使数量与服务端数量一致
const displayMedia = media.concat(
new Array(mediaAmount - media.length).fill(media[0])
);
const { showImageViewer } = useImageViewer();
const images = displayMedia.map((item) => {
return { url: item.urls[0] };
});
const [isCollapsed, setIsCollapsed] = useState(true);
if (images.length === 0) return null;
return (
<View style={tailwind("flex flex-row flex-wrap")}>
{displayMedia.map((item, index) => {
if (index > 8 && isCollapsed) return null;
return (
<TouchableOpacity
activeOpacity={1}
key={index}
onPress={() => {
showImageViewer({
imageUrls: images,
index: index,
lockedStartIndex: isUnlocked ? 999 : visibleRange,
onPressUnlockBtn: () =>
navigation.navigate("WebWithoutHeader", {
uri:
process.env.EXPO_PUBLIC_WEB_URL +
"/zone/pay/" +
data?.zid +
"/h5_zone_moment/" +
data?.id,
}),
});
}}
style={
displayMedia.length > 1
? { aspectRatio: 1, ...tailwind("basis-1/3 p-0.5") }
: {
width:
displayMedia[0].w < displayMedia[0].h
? (displayMedia[0].w / displayMedia[0].h) * 200
: 250,
height:
displayMedia[0].w < displayMedia[0].h
? 200
: (displayMedia[0].h / displayMedia[0].w) * 250,
}
}
>
<Image
style={tailwind("w-full h-full rounded")}
blurRadius={
!isUnlocked && visibleRange < index + 1
? Platform.OS === "ios"
? 100
: 10
: 0
}
source={item.urls[0]}
placeholder={blurhash}
contentFit="cover"
contentPosition={
displayMedia.length === 1 ? "left top" : "center"
}
transition={100}
cachePolicy="disk"
/>
{isCreator && visibleRange < index + 1 && (
<View
style={{
marginLeft: 2,
marginBottom: 2,
borderBottomRightRadius: 4,
borderBottomLeftRadius: 4,
...tailwind(
"absolute flex flex-row justify-center w-full bottom-0 bg-[#00000080]"
),
}}
>
<Text style={tailwind("text-xs text-white font-medium")}>
不可预览
</Text>
</View>
)}
{index === 8 && isCollapsed && (
<TouchableOpacity
activeOpacity={1}
onPress={() => setIsCollapsed(false)}
style={{
marginLeft: 2,
marginTop: 2,
...tailwind(
"absolute flex w-full h-full items-center justify-center bg-[#00000099]"
),
}}
>
<Text style={tailwind("text-white text-2xl font-medium")}>
+{media.length - 9}
</Text>
</TouchableOpacity>
)}
{index === media.length - 1 && !isCollapsed && (
<TouchableOpacity
activeOpacity={1}
onPress={() => setIsCollapsed(true)}
style={{
marginLeft: 2,
marginTop: 2,
...tailwind(
"absolute flex w-full h-full items-center justify-center bg-[#00000099]"
),
}}
>
<Icon
type="ionicon"
name="chevron-up"
size={32}
color="white"
/>
</TouchableOpacity>
)}
</TouchableOpacity>
);
})}
</View>
);
}
//媒体为视频时展示封面的组件
function PosterDisplay({ isUnlocked, blurCover, media }) {
const tailwind = useTailwind();
return (
<View
style={{
width:
media.cover_w < media.cover_h
? (media.cover_w / media.cover_h) * 200
: 250,
height:
media.cover_w < media.cover_h
? 200
: (media.cover_h / media.cover_w) * 250,
}}
>
<Image
style={tailwind("w-full h-full rounded")}
blurRadius={
!isUnlocked && blurCover === 1
? Platform.OS === "ios"
? 100
: 10
: 0
}
source={media.cover_urls[0]}
placeholder={blurhash}
contentFit="cover"
transition={100}
cachePolicy="disk"
/>
<View
style={tailwind(
"flex absolute w-full h-full left-0 top-0 z-10 justify-center items-center"
)}
>
<NativeImage source={require("../../assets/icon/others/play.png")} />
</View>
</View>
);
}
//帖子操作组件
function OperationModal({ visible, setVisible, data }) {
const tailwind = useTailwind();
const navigation = useNavigation();
//拉黑
const handleBlock = async () => {
const account = await get("account");
const subMid = account.mid;
const objMid = data.mid;
await block(subMid, objMid);
setVisible(false);
};
//删除
const handleDelete = async () => {
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
try {
const base = await baseRequest();
const signature = await generateSignature({
id: data.id,
...base,
});
const _response = await fetch(
`${apiUrl}/api/zone_moment/delete?signature=${signature}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
id: data.id,
...base,
}),
}
);
const _data = await _response.json();
if (_data.ret === -1) {
Toast.show({
type: "error",
text1: _data.msg,
topOffset: 60,
});
return;
}
Toast.show({
type: "success",
text1: "删除成功,请刷新页面",
topOffset: 60,
});
return;
} catch (error) {
console.error(error);
} finally {
setVisible(false);
}
};
//置顶
const handleTop = async () => {
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
try {
const base = await baseRequest();
const body = {
zone_moment_ids: [data.id],
op_type: data.is_headed ? 0 : 1,
...base,
};
const signature = await generateSignature(body);
const _response = await fetch(
`${apiUrl}/api/zone_moment/head?signature=${signature}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
}
);
const _data = await _response.json();
if (_data.ret === -1) {
Toast.show({
type: "error",
text1: _data.msg,
topOffset: 60,
});
return;
}
Toast.show({
type: "success",
text1: "操作成功,请刷新页面查看",
topOffset: 60,
});
return;
} catch (error) {
console.error(error);
} finally {
setVisible(false);
}
};
//判断是否展示删除、拉黑、举报功能
const [display, setDisPlay] = useState({
delete: false,
top: false,
block: true,
feedback: true,
});
useEffect(() => {
const checkAuth = async () => {
const account = await get("account");
if (account.mid === data.mid || account.role === 1 || account.role === 2)
setDisPlay((prev) => {
return { ...prev, delete: true, top: true };
});
if (account.mid === data.mid)
setDisPlay((prev) => {
return { ...prev, block: false, feedback: false };
});
};
checkAuth();
}, []);
return (
<Modal
visible={visible}
statusBarTranslucent
transparent={true}
style={tailwind("flex-1")}
>
<TouchableOpacity
activeOpacity={1}
onPress={() => setVisible(false)}
style={{
backgroundColor: "#00000080",
...tailwind("flex flex-1 items-center justify-center"),
}}
>
<TouchableOpacity
activeOpacity={1}
style={tailwind(
"flex flex-col p-2 rounded-2xl bg-[#1E1C29] items-center justify-center w-3/4"
)}
>
{display.feedback && (
<View style={tailwind("flex flex-col w-full items-center")}>
<TouchableOpacity
onPress={() => {
navigation.navigate("MessageDetail", { mid: 1 });
setVisible(false);
}}
style={tailwind("flex flex-col w-full py-2 items-center")}
>
<Text style={tailwind("text-base text-white font-medium")}>
举报
</Text>
</TouchableOpacity>
<View
style={tailwind(
"h-[1px] w-full rounded-full mx-4 bg-[#FFFFFF26]"
)}
></View>
</View>
)}
{display.block && (
<View style={tailwind("flex flex-col w-full items-center")}>
<TouchableOpacity
onPress={handleBlock}
style={tailwind("flex flex-col w-full py-2 items-center")}
>
<Text style={tailwind("text-base text-white font-medium")}>
拉黑
</Text>
</TouchableOpacity>
<View
style={tailwind(
"h-[1px] w-full rounded-full mx-4 bg-[#FFFFFF26]"
)}
></View>
</View>
)}
{display.top && (
<View style={tailwind("flex flex-col w-full items-center")}>
<TouchableOpacity
onPress={handleTop}
style={tailwind("flex flex-col w-full py-2 items-center")}
>
<Text style={tailwind("text-base text-white font-medium")}>
{data.is_headed ? "取消置顶" : "置顶"}
</Text>
</TouchableOpacity>
<View
style={tailwind(
"h-[1px] w-full rounded-full mx-4 bg-[#FFFFFF26]"
)}
></View>
</View>
)}
{display.delete && (
<View style={tailwind("flex flex-col w-full items-center")}>
<TouchableOpacity
onPress={handleDelete}
style={tailwind("flex flex-col w-full py-2 items-center")}
>
<Text style={tailwind("text-base text-white font-medium")}>
删除
</Text>
</TouchableOpacity>
<View
style={tailwind(
"h-[1px] w-full rounded-full mx-4 bg-[#FFFFFF26]"
)}
></View>
</View>
)}
<TouchableOpacity
onPress={() => setVisible(false)}
style={tailwind("flex flex-col w-full py-2 items-center")}
>
<Text style={tailwind("text-base text-white font-medium")}>
关闭
</Text>
</TouchableOpacity>
</TouchableOpacity>
</TouchableOpacity>
</Modal>
);
}