|
|
|
@ -16,7 +16,7 @@ import {
|
|
|
|
|
Platform, |
|
|
|
|
StatusBar, |
|
|
|
|
SafeAreaView, |
|
|
|
|
Modal |
|
|
|
|
Modal, |
|
|
|
|
} from "react-native"; |
|
|
|
|
import fontSize from "../utils/fontsizeUtils"; |
|
|
|
|
import widthUtils from "../utils/widthUtils"; |
|
|
|
@ -29,13 +29,12 @@ import HeartRedIcon from "../components/HeartIconRed";
|
|
|
|
|
import ShareIcon from "../components/ShareIcon"; |
|
|
|
|
import { Image as ExpoImage, ImageBackground } from "expo-image"; |
|
|
|
|
import useUserStore from "../store/user"; |
|
|
|
|
import { getSubjectTransLanguage } from "../utils/languageUtils"; |
|
|
|
|
import { getSubjectTransLanguage,getSkuTransLanguage } from "../utils/languageUtils"; |
|
|
|
|
import { useTranslation } from "react-i18next"; |
|
|
|
|
import { getBurialPointData } from "../store/burialPoint"; |
|
|
|
|
import useBurialPointStore from "../store/burialPoint"; |
|
|
|
|
import * as ImagePicker from "expo-image-picker"; |
|
|
|
|
import * as FileSystem from "expo-file-system"; |
|
|
|
|
|
|
|
|
|
import { |
|
|
|
|
productApi, |
|
|
|
|
ProductDetailParams, |
|
|
|
@ -91,18 +90,18 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
// 遍历数据
|
|
|
|
|
res.skus.forEach((item) => { |
|
|
|
|
item.attributes.forEach((attribute) => { |
|
|
|
|
const { attribute_name, value } = attribute; |
|
|
|
|
const { attribute_name_trans, value } = attribute; |
|
|
|
|
// 如果结果对象中没有对应的属性名,则创建一个空数组
|
|
|
|
|
if (!result[attribute_name]) { |
|
|
|
|
result[attribute_name] = []; |
|
|
|
|
if (!result[attribute_name_trans]) { |
|
|
|
|
result[attribute_name_trans] = []; |
|
|
|
|
} |
|
|
|
|
// 如果当前属性的值(value)已经存在于该组内,跳过
|
|
|
|
|
if ( |
|
|
|
|
!result[attribute_name].some( |
|
|
|
|
!result[attribute_name_trans].some( |
|
|
|
|
(existingAttribute: any) => existingAttribute.value === value |
|
|
|
|
) |
|
|
|
|
) { |
|
|
|
|
result[attribute_name].push(attribute); |
|
|
|
|
result[attribute_name_trans].push(attribute); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
@ -128,7 +127,10 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
// Add has_image to the list item
|
|
|
|
|
list.push({ |
|
|
|
|
attribute_name: attributeName, |
|
|
|
|
has_image: withImage.length > 0, // If there are any items with images, set has_image to true
|
|
|
|
|
attribute_name_trans: attributeName, |
|
|
|
|
attribute_name_trans_ar: attributeName, |
|
|
|
|
attribute_name_trans_en: attributeName, |
|
|
|
|
has_image: withImage.length > 0, |
|
|
|
|
attributes: [...withImage, ...withoutImage], |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
@ -326,8 +328,8 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
setImageUrls(limitedImageUrls); |
|
|
|
|
setGroupList(list); |
|
|
|
|
|
|
|
|
|
const previousScreen = navigation.getState().routes[navigation.getState().index - 1]; |
|
|
|
|
|
|
|
|
|
const previousScreen = |
|
|
|
|
navigation.getState().routes[navigation.getState().index - 1]; |
|
|
|
|
const data = { |
|
|
|
|
offer_id: res.offer_id, |
|
|
|
|
category_id: res.category_id, |
|
|
|
@ -337,12 +339,9 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
product_name: res.subject, |
|
|
|
|
product_img: res.product_image_urls[0], |
|
|
|
|
timestamp: new Date().toISOString(), |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
logViewProduct(data, previousScreen?.name as string); |
|
|
|
|
|
|
|
|
|
console.log(getBurialPointData()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) { |
|
|
|
|
console.error("Error fetching product details:", error); |
|
|
|
|
} finally { |
|
|
|
@ -389,7 +388,6 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
navigation.navigate("Search"); |
|
|
|
|
}, [navigation]); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handleCameraPress = useCallback(() => { |
|
|
|
|
setShowImagePickerModal(true); |
|
|
|
|
}, []); |
|
|
|
@ -404,11 +402,12 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
}, |
|
|
|
|
[navigation] |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
// Add this function to render skeleton UI
|
|
|
|
|
const renderSkeletonItems = () => { |
|
|
|
|
// Create an array of 5 skeleton items
|
|
|
|
|
return Array(5).fill(0).map((_, index) => ( |
|
|
|
|
return Array(5) |
|
|
|
|
.fill(0) |
|
|
|
|
.map((_, index) => ( |
|
|
|
|
<View style={styles.productCard} key={`skeleton-${index}`}> |
|
|
|
|
<View style={[styles.cardContainerWithPrice, styles.skeletonBox]} /> |
|
|
|
|
<View style={styles.priceContainerFlex}> |
|
|
|
@ -418,13 +417,12 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
</View> |
|
|
|
|
)); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// 清理expo-image-picker临时文件
|
|
|
|
|
const cleanupImagePickerCache = async () => { |
|
|
|
|
try { |
|
|
|
|
// Skip cache cleanup on web platform
|
|
|
|
|
if (Platform.OS === 'web') { |
|
|
|
|
console.log('Cache cleanup skipped on web platform'); |
|
|
|
|
if (Platform.OS === "web") { |
|
|
|
|
console.log("Cache cleanup skipped on web platform"); |
|
|
|
|
setGalleryUsed(false); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
@ -433,7 +431,6 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
const cacheDir = `${FileSystem.cacheDirectory}ImagePicker`; |
|
|
|
|
await FileSystem.deleteAsync(cacheDir, { idempotent: true }); |
|
|
|
|
console.log("已清理ImagePicker缓存"); |
|
|
|
|
|
|
|
|
|
// 立即重置状态,无需用户干预
|
|
|
|
|
setGalleryUsed(false); |
|
|
|
|
} catch (error) { |
|
|
|
@ -442,12 +439,10 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
setGalleryUsed(false); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// 处理从相册选择
|
|
|
|
|
const handleChooseFromGallery = useCallback(async () => { |
|
|
|
|
console.log("handleChooseFromGallery"); |
|
|
|
|
setShowImagePickerModal(false); |
|
|
|
|
|
|
|
|
|
// 等待模态窗关闭后再执行
|
|
|
|
|
setTimeout(async () => { |
|
|
|
|
try { |
|
|
|
@ -458,7 +453,6 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
console.log("相册权限被拒绝"); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 打开相册
|
|
|
|
|
const result = await ImagePicker.launchImageLibraryAsync({ |
|
|
|
|
mediaTypes: ImagePicker.MediaTypeOptions.Images, |
|
|
|
@ -466,10 +460,8 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
aspect: [4, 3], |
|
|
|
|
quality: 1, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
if (!result.canceled && result.assets && result.assets.length > 0) { |
|
|
|
|
console.log("相册选择成功:", result.assets[0].uri); |
|
|
|
|
|
|
|
|
|
await cleanupImagePickerCache(); |
|
|
|
|
navigation.navigate("ImageSearchResultScreen", { |
|
|
|
|
image: result.assets[0].uri, |
|
|
|
@ -483,12 +475,10 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
} |
|
|
|
|
}, 500); |
|
|
|
|
}, [navigation, userStore.user]); |
|
|
|
|
|
|
|
|
|
// 处理相机拍照
|
|
|
|
|
const handleTakePhoto = useCallback(async () => { |
|
|
|
|
console.log("handleTakePhoto"); |
|
|
|
|
setShowImagePickerModal(false); |
|
|
|
|
|
|
|
|
|
// 等待模态窗关闭后再执行
|
|
|
|
|
setTimeout(async () => { |
|
|
|
|
try { |
|
|
|
@ -498,17 +488,14 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
console.log("相机权限被拒绝"); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const result = await ImagePicker.launchCameraAsync({ |
|
|
|
|
mediaTypes: ImagePicker.MediaTypeOptions.Images, |
|
|
|
|
allowsEditing: true, |
|
|
|
|
aspect: [4, 3], |
|
|
|
|
quality: 1, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
if (!result.canceled && result.assets && result.assets.length > 0) { |
|
|
|
|
console.log("拍照成功:", result.assets[0].uri); |
|
|
|
|
|
|
|
|
|
// 使用后清理缓存
|
|
|
|
|
await cleanupImagePickerCache(); |
|
|
|
|
navigation.navigate("ImageSearchResultScreen", { |
|
|
|
@ -523,19 +510,15 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
} |
|
|
|
|
}, 500); |
|
|
|
|
}, [navigation, userStore.user]); |
|
|
|
|
|
|
|
|
|
// 重置应用状态函数
|
|
|
|
|
const resetAppState = useCallback(() => { |
|
|
|
|
// 重置标记
|
|
|
|
|
setGalleryUsed(false); |
|
|
|
|
|
|
|
|
|
// 清理缓存
|
|
|
|
|
cleanupImagePickerCache(); |
|
|
|
|
|
|
|
|
|
// 提示用户
|
|
|
|
|
Alert.alert("已重置", "现在您可以使用相机功能了"); |
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<SafeAreaView style={styles.safeArea}> |
|
|
|
|
<StatusBar barStyle="dark-content" backgroundColor="#fff" /> |
|
|
|
@ -561,12 +544,11 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
onPress={handleSearchPress} |
|
|
|
|
activeOpacity={0.7} |
|
|
|
|
> |
|
|
|
|
<View style={{ width: '100%', alignItems: 'flex-start' }}> |
|
|
|
|
<View style={{ width: "100%", alignItems: "flex-start" }}> |
|
|
|
|
<Text style={styles.searchText}>{t("search")}</Text> |
|
|
|
|
</View> |
|
|
|
|
</TouchableOpacity> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<TouchableOpacity |
|
|
|
|
style={styles.searchIcon} |
|
|
|
|
onPress={() => { |
|
|
|
@ -660,7 +642,8 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
}} |
|
|
|
|
> |
|
|
|
|
<Text style={styles.activeIndexText}> |
|
|
|
|
{activeIndex + 1}/{product?.product_image_urls?.length} |
|
|
|
|
{activeIndex + 1}/ |
|
|
|
|
{product?.product_image_urls?.length} |
|
|
|
|
</Text> |
|
|
|
|
</View> |
|
|
|
|
</View> |
|
|
|
@ -693,9 +676,10 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
{userStore.user?.vip_level > 0 && ( |
|
|
|
|
<> |
|
|
|
|
<View style={styles.discountInfoContainer}> |
|
|
|
|
<Text style={styles.emphasizedTextWidget}>-5%</Text> |
|
|
|
|
<Text style={styles.emphasizedTextWidget}> |
|
|
|
|
-5% |
|
|
|
|
</Text> |
|
|
|
|
</View> |
|
|
|
|
|
|
|
|
|
<View style={styles.priceInfoVip}> |
|
|
|
|
<ImageBackground |
|
|
|
|
source={require("../../assets/img/vip1.png")} |
|
|
|
@ -715,23 +699,28 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
<View style={styles.blackThemeContainer}> |
|
|
|
|
{groupList.map((item, index) => |
|
|
|
|
item.has_image ? ( |
|
|
|
|
<View key={item.attribute_name}> |
|
|
|
|
<View key={item.attribute_name_trans}> |
|
|
|
|
<Text style={styles.uniqueTextBlock}> |
|
|
|
|
{item.attribute_name} :{" "} |
|
|
|
|
{item.attribute_name_trans} :{" "} |
|
|
|
|
{ |
|
|
|
|
item.attributes.find((item) => item.has_color) |
|
|
|
|
?.value |
|
|
|
|
// getSkuTransLanguage()
|
|
|
|
|
item.attributes.find( |
|
|
|
|
(item) => item.has_color |
|
|
|
|
)?.value |
|
|
|
|
} |
|
|
|
|
</Text> |
|
|
|
|
<View style={styles.horizontalFlexContainer}> |
|
|
|
|
{getDisplayAttributes( |
|
|
|
|
item.attributes, |
|
|
|
|
item.attribute_name |
|
|
|
|
item.attribute_name_trans |
|
|
|
|
).map((attribute) => ( |
|
|
|
|
<TouchableOpacity |
|
|
|
|
key={attribute.value} |
|
|
|
|
onPress={() => |
|
|
|
|
handleColorSelect(attribute.value, index) |
|
|
|
|
handleColorSelect( |
|
|
|
|
attribute.value, |
|
|
|
|
index |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
style={[ |
|
|
|
|
styles.colorImageContainer, |
|
|
|
@ -740,17 +729,19 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
]} |
|
|
|
|
> |
|
|
|
|
<Image |
|
|
|
|
source={{ uri: attribute.sku_image_url }} |
|
|
|
|
source={{ |
|
|
|
|
uri: attribute.sku_image_url, |
|
|
|
|
}} |
|
|
|
|
style={styles.imageContainer} |
|
|
|
|
/> |
|
|
|
|
</TouchableOpacity> |
|
|
|
|
))} |
|
|
|
|
{!expandedGroups[item.attribute_name] && |
|
|
|
|
{!expandedGroups[item.attribute_name_trans] && |
|
|
|
|
item.attributes.length > 6 && ( |
|
|
|
|
<TouchableOpacity |
|
|
|
|
style={styles.expandButton} |
|
|
|
|
onPress={() => |
|
|
|
|
toggleExpand(item.attribute_name) |
|
|
|
|
toggleExpand(item.attribute_name_trans) |
|
|
|
|
} |
|
|
|
|
> |
|
|
|
|
<Text style={styles.expandButtonText}> |
|
|
|
@ -758,11 +749,11 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
</Text> |
|
|
|
|
</TouchableOpacity> |
|
|
|
|
)} |
|
|
|
|
{expandedGroups[item.attribute_name] && ( |
|
|
|
|
{expandedGroups[item.attribute_name_trans] && ( |
|
|
|
|
<TouchableOpacity |
|
|
|
|
style={styles.expandButton} |
|
|
|
|
onPress={() => |
|
|
|
|
toggleExpand(item.attribute_name) |
|
|
|
|
toggleExpand(item.attribute_name_trans) |
|
|
|
|
} |
|
|
|
|
> |
|
|
|
|
<Text style={styles.expandButtonText}> |
|
|
|
@ -793,14 +784,14 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
)} |
|
|
|
|
</View> |
|
|
|
|
) : ( |
|
|
|
|
<View key={item.attribute_name}> |
|
|
|
|
<View key={item.attribute_name_trans}> |
|
|
|
|
<Text style={styles.uniqueTextBlock}> |
|
|
|
|
{item.attribute_name} |
|
|
|
|
{item.attribute_name_trans} |
|
|
|
|
</Text> |
|
|
|
|
<View style={styles.horizontalFlexContainer}> |
|
|
|
|
{getDisplayAttributes( |
|
|
|
|
item.attributes, |
|
|
|
|
item.attribute_name |
|
|
|
|
item.attribute_name_trans |
|
|
|
|
).map((attribute) => ( |
|
|
|
|
<TouchableOpacity |
|
|
|
|
key={attribute.value} |
|
|
|
@ -824,12 +815,12 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
</Text> |
|
|
|
|
</TouchableOpacity> |
|
|
|
|
))} |
|
|
|
|
{!expandedGroups[item.attribute_name] && |
|
|
|
|
{!expandedGroups[item.attribute_name_trans] && |
|
|
|
|
item.attributes.length > 6 && ( |
|
|
|
|
<TouchableOpacity |
|
|
|
|
style={styles.expandButton} |
|
|
|
|
onPress={() => |
|
|
|
|
toggleExpand(item.attribute_name) |
|
|
|
|
toggleExpand(item.attribute_name_trans) |
|
|
|
|
} |
|
|
|
|
> |
|
|
|
|
<Text style={styles.expandButtonText}> |
|
|
|
@ -837,11 +828,11 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
</Text> |
|
|
|
|
</TouchableOpacity> |
|
|
|
|
)} |
|
|
|
|
{expandedGroups[item.attribute_name] && ( |
|
|
|
|
{expandedGroups[item.attribute_name_trans] && ( |
|
|
|
|
<TouchableOpacity |
|
|
|
|
style={styles.expandButton} |
|
|
|
|
onPress={() => |
|
|
|
|
toggleExpand(item.attribute_name) |
|
|
|
|
toggleExpand(item.attribute_name_trans) |
|
|
|
|
} |
|
|
|
|
> |
|
|
|
|
<Text style={styles.expandButtonText}> |
|
|
|
@ -888,8 +879,8 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
showsHorizontalScrollIndicator={false} |
|
|
|
|
contentContainerStyle={styles.productGridContainer} |
|
|
|
|
> |
|
|
|
|
{isSimilarsFlag ?
|
|
|
|
|
similars?.map((item) => ( |
|
|
|
|
{isSimilarsFlag |
|
|
|
|
? similars?.map((item) => ( |
|
|
|
|
<TouchableOpacity |
|
|
|
|
style={styles.productCard} |
|
|
|
|
key={item.offer_id} |
|
|
|
@ -905,14 +896,15 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
<Text style={styles.highlightedText1}> |
|
|
|
|
{item.max_price} |
|
|
|
|
</Text> |
|
|
|
|
<Text style={styles.highlightedTextWithBorder}> |
|
|
|
|
<Text |
|
|
|
|
style={styles.highlightedTextWithBorder} |
|
|
|
|
> |
|
|
|
|
FCFA |
|
|
|
|
</Text> |
|
|
|
|
</View> |
|
|
|
|
</TouchableOpacity> |
|
|
|
|
)) |
|
|
|
|
: renderSkeletonItems() |
|
|
|
|
} |
|
|
|
|
: renderSkeletonItems()} |
|
|
|
|
</ScrollView> |
|
|
|
|
</View> |
|
|
|
|
<View style={{ width: "100%" }}> |
|
|
|
@ -994,16 +986,13 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
<Text style={styles.imagePickerText}>重置相机功能</Text> |
|
|
|
|
</TouchableOpacity> |
|
|
|
|
)} |
|
|
|
|
|
|
|
|
|
<View style={styles.imagePickerDivider} /> |
|
|
|
|
|
|
|
|
|
<TouchableOpacity |
|
|
|
|
style={styles.imagePickerOption} |
|
|
|
|
onPress={handleChooseFromGallery} |
|
|
|
|
> |
|
|
|
|
<Text style={styles.imagePickerText}>从相册选择</Text> |
|
|
|
|
</TouchableOpacity> |
|
|
|
|
|
|
|
|
|
<TouchableOpacity |
|
|
|
|
style={styles.imagePickerCancelButton} |
|
|
|
|
onPress={() => setShowImagePickerModal(false)} |
|
|
|
@ -1019,15 +1008,15 @@ export const ProductDetailScreen = () => {
|
|
|
|
|
const styles = StyleSheet.create({ |
|
|
|
|
safeArea: { |
|
|
|
|
flex: 1, |
|
|
|
|
backgroundColor: '#fff', |
|
|
|
|
backgroundColor: "#fff", |
|
|
|
|
}, |
|
|
|
|
safeAreaContent: { |
|
|
|
|
flex: 1, |
|
|
|
|
paddingTop: Platform.OS === 'android' ? 0 : 0, |
|
|
|
|
paddingTop: Platform.OS === "android" ? 0 : 0, |
|
|
|
|
}, |
|
|
|
|
container: { |
|
|
|
|
flex: 1, |
|
|
|
|
backgroundColor: '#fff', |
|
|
|
|
backgroundColor: "#fff", |
|
|
|
|
}, |
|
|
|
|
scrollView: { |
|
|
|
|
flex: 1, |
|
|
|
@ -1306,7 +1295,7 @@ const styles = StyleSheet.create({
|
|
|
|
|
fontSize: fontSize(18), |
|
|
|
|
color: "#f1c355", |
|
|
|
|
textAlign: "center", |
|
|
|
|
marginLeft:2 |
|
|
|
|
marginLeft: 2, |
|
|
|
|
}, |
|
|
|
|
emphasizedTextVip: { |
|
|
|
|
fontStyle: "italic", |
|
|
|
@ -1733,14 +1722,14 @@ const styles = StyleSheet.create({
|
|
|
|
|
color: "#666", |
|
|
|
|
}, |
|
|
|
|
skeletonBox: { |
|
|
|
|
backgroundColor: '#e0e0e0', |
|
|
|
|
backgroundColor: "#e0e0e0", |
|
|
|
|
height: widthUtils(90, 90).height, |
|
|
|
|
width: widthUtils(90, 90).width, |
|
|
|
|
borderRadius: 5, |
|
|
|
|
}, |
|
|
|
|
skeletonText: { |
|
|
|
|
height: 16, |
|
|
|
|
backgroundColor: '#e0e0e0', |
|
|
|
|
backgroundColor: "#e0e0e0", |
|
|
|
|
borderRadius: 3, |
|
|
|
|
}, |
|
|
|
|
imagePickerOverlay: { |
|
|
|
|