|
|
|
@ -9,13 +9,17 @@ import {
|
|
|
|
|
Image, |
|
|
|
|
ScrollView, |
|
|
|
|
Modal, |
|
|
|
|
ImageBackground, |
|
|
|
|
RefreshControl, |
|
|
|
|
Dimensions, |
|
|
|
|
Animated, |
|
|
|
|
Platform, |
|
|
|
|
StatusBar, |
|
|
|
|
SafeAreaView, |
|
|
|
|
ViewStyle, |
|
|
|
|
TextStyle, |
|
|
|
|
ImageStyle, |
|
|
|
|
Linking, |
|
|
|
|
Alert, |
|
|
|
|
} from "react-native"; |
|
|
|
|
import { |
|
|
|
|
productApi, |
|
|
|
@ -35,6 +39,8 @@ import CloseIcon from "../components/CloseIcon";
|
|
|
|
|
import CheckmarkIcon from "../components/CheckmarkIcon"; |
|
|
|
|
import { getSubjectTransLanguage } from "../utils/languageUtils"; |
|
|
|
|
import useUserStore from "../store/user"; |
|
|
|
|
import * as ImagePicker from 'expo-image-picker'; |
|
|
|
|
import * as FileSystem from 'expo-file-system'; |
|
|
|
|
// 为图标定义类型
|
|
|
|
|
type IconProps = { |
|
|
|
|
name: string; |
|
|
|
@ -200,12 +206,102 @@ const ProductSkeleton = React.memo(() => {
|
|
|
|
|
); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// Define the styles type to fix TypeScript errors
|
|
|
|
|
type StylesType = { |
|
|
|
|
safeArea: ViewStyle; |
|
|
|
|
safeAreaContent: ViewStyle; |
|
|
|
|
container: ViewStyle; |
|
|
|
|
swpImg: ImageStyle; |
|
|
|
|
searchOverlay: ViewStyle; |
|
|
|
|
searchBar: ViewStyle; |
|
|
|
|
searchPlaceholder: TextStyle; |
|
|
|
|
cameraButton: ViewStyle; |
|
|
|
|
bannerContainer: ViewStyle; |
|
|
|
|
leftContainer: ViewStyle; |
|
|
|
|
leftTopItem: ViewStyle; |
|
|
|
|
leftBottomItem: ViewStyle; |
|
|
|
|
rightContainer: ViewStyle; |
|
|
|
|
bannerIcon: ImageStyle; |
|
|
|
|
bigbannerIcon: ImageStyle; |
|
|
|
|
category: ViewStyle; |
|
|
|
|
categoryScrollContainer: ViewStyle; |
|
|
|
|
categoryScroll: ViewStyle; |
|
|
|
|
categoryItem: ViewStyle; |
|
|
|
|
categoryItemActive: ViewStyle; |
|
|
|
|
categoryText: TextStyle; |
|
|
|
|
categoryTextActive: TextStyle; |
|
|
|
|
swiperContainer: ViewStyle; |
|
|
|
|
swiper: ViewStyle; |
|
|
|
|
dot: ViewStyle; |
|
|
|
|
activeDot: ViewStyle; |
|
|
|
|
slide: ViewStyle; |
|
|
|
|
slideImage: ImageStyle; |
|
|
|
|
fadeGradient: ViewStyle; |
|
|
|
|
categoryArrowContainer: ViewStyle; |
|
|
|
|
modalOverlay: ViewStyle; |
|
|
|
|
modalContent: ViewStyle; |
|
|
|
|
modalHeader: ViewStyle; |
|
|
|
|
modalTitleContainer: ViewStyle; |
|
|
|
|
modalTitle: TextStyle; |
|
|
|
|
closeButton: ViewStyle; |
|
|
|
|
closeButtonText: TextStyle; |
|
|
|
|
modalScrollView: ViewStyle; |
|
|
|
|
categoryModalItem: ViewStyle; |
|
|
|
|
categoryModalText: TextStyle; |
|
|
|
|
selectedCategoryText: TextStyle; |
|
|
|
|
subcategoryContainer: ViewStyle; |
|
|
|
|
subcategoryScroll: ViewStyle; |
|
|
|
|
subcategoryContent: ViewStyle; |
|
|
|
|
subcategoryItem: ViewStyle; |
|
|
|
|
subcategoryImagePlaceholder: ViewStyle; |
|
|
|
|
subcategoryText: TextStyle; |
|
|
|
|
productContainer: ViewStyle; |
|
|
|
|
productCardList: ViewStyle; |
|
|
|
|
productCardGroup: ViewStyle; |
|
|
|
|
beautyProductCard1: ViewStyle; |
|
|
|
|
beautyCardContainer1: ViewStyle; |
|
|
|
|
vipButtonContainer: ViewStyle; |
|
|
|
|
vipButton: ViewStyle; |
|
|
|
|
vipButtonText: TextStyle; |
|
|
|
|
vipLabelBold: TextStyle; |
|
|
|
|
beautyProductCard: ViewStyle; |
|
|
|
|
beautyProductTitle: TextStyle; |
|
|
|
|
beautyProductInfoRow: ViewStyle; |
|
|
|
|
flexRowCentered: ViewStyle; |
|
|
|
|
priceContainer: ViewStyle; |
|
|
|
|
highlightedText: TextStyle; |
|
|
|
|
highlightedText1: TextStyle; |
|
|
|
|
priceContainer1: ViewStyle; |
|
|
|
|
priceLabel1: TextStyle; |
|
|
|
|
beautySalesInfo: TextStyle; |
|
|
|
|
indicatorContainer: ViewStyle; |
|
|
|
|
indicator: ViewStyle; |
|
|
|
|
activeIndicator: ViewStyle; |
|
|
|
|
inactiveIndicator: ViewStyle; |
|
|
|
|
skeletonContainer: ViewStyle; |
|
|
|
|
skeletonImage: ViewStyle; |
|
|
|
|
skeletonTitle: ViewStyle; |
|
|
|
|
skeletonPrice: ViewStyle; |
|
|
|
|
skeletonSales: ViewStyle; |
|
|
|
|
shimmer: ViewStyle; |
|
|
|
|
imagePlaceholder: ViewStyle; |
|
|
|
|
productImage: ImageStyle; |
|
|
|
|
imagePickerOverlay: ViewStyle; |
|
|
|
|
imagePickerContent: ViewStyle; |
|
|
|
|
imagePickerOption: ViewStyle; |
|
|
|
|
imagePickerText: TextStyle; |
|
|
|
|
imagePickerDivider: ViewStyle; |
|
|
|
|
imagePickerCancelButton: ViewStyle; |
|
|
|
|
imagePickerCancelText: TextStyle; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
export const HomeScreen = () => { |
|
|
|
|
const [activeIndex, setActiveIndex] = useState(0); |
|
|
|
|
const screenWidth = Dimensions.get("window").width; |
|
|
|
|
const navigation = useNavigation<NativeStackNavigationProp<any>>(); |
|
|
|
|
const { t } = useTranslation(); |
|
|
|
|
const [showCategoryModal, setShowCategoryModal] = useState(false); |
|
|
|
|
const [showImagePickerModal, setShowImagePickerModal] = useState(false); |
|
|
|
|
const [selectedCategory, setSelectedCategory] = useState("Bijoux"); |
|
|
|
|
const [selectedHorizontalCategory, setSelectedHorizontalCategory] = |
|
|
|
|
useState("Tous"); |
|
|
|
@ -237,6 +333,7 @@ export const HomeScreen = () => {
|
|
|
|
|
add: "CompanyScreen", |
|
|
|
|
}, |
|
|
|
|
]; |
|
|
|
|
const [galleryUsed, setGalleryUsed] = useState(false); // 标记是否使用过相册
|
|
|
|
|
const getProductData = async () => { |
|
|
|
|
setLoading(true); // 开始加载,显示骨架屏
|
|
|
|
|
try { |
|
|
|
@ -369,7 +466,7 @@ export const HomeScreen = () => {
|
|
|
|
|
resizeMode="cover" |
|
|
|
|
/> |
|
|
|
|
) : ( |
|
|
|
|
<View style={[styles.productImage, styles.imagePlaceholder]}> |
|
|
|
|
<View style={[styles.productImage as any, styles.imagePlaceholder]}> |
|
|
|
|
<IconComponent name="image-outline" size={24} color="#999" /> |
|
|
|
|
</View> |
|
|
|
|
)} |
|
|
|
@ -438,6 +535,109 @@ export const HomeScreen = () => {
|
|
|
|
|
); |
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
// 清理expo-image-picker临时文件
|
|
|
|
|
const cleanupImagePickerCache = async () => { |
|
|
|
|
try { |
|
|
|
|
// 相册选择后清理临时缓存
|
|
|
|
|
const cacheDir = `${FileSystem.cacheDirectory}ImagePicker`; |
|
|
|
|
await FileSystem.deleteAsync(cacheDir, { idempotent: true }); |
|
|
|
|
console.log('已清理ImagePicker缓存'); |
|
|
|
|
|
|
|
|
|
// 立即重置状态,无需用户干预
|
|
|
|
|
setGalleryUsed(false); |
|
|
|
|
} catch (error) { |
|
|
|
|
console.log('清理缓存错误', error); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// 处理从相册选择
|
|
|
|
|
const handleChooseFromGallery = useCallback(async () => { |
|
|
|
|
console.log('handleChooseFromGallery'); |
|
|
|
|
setShowImagePickerModal(false); |
|
|
|
|
|
|
|
|
|
// 等待模态窗关闭后再执行
|
|
|
|
|
setTimeout(async () => { |
|
|
|
|
try { |
|
|
|
|
// 请求相册权限
|
|
|
|
|
const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync(); |
|
|
|
|
if (permissionResult.status !== 'granted') { |
|
|
|
|
console.log('相册权限被拒绝'); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 打开相册
|
|
|
|
|
const result = await ImagePicker.launchImageLibraryAsync({ |
|
|
|
|
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(); |
|
|
|
|
} |
|
|
|
|
} catch (error: any) { |
|
|
|
|
console.error('相册错误:', error); |
|
|
|
|
// 出错时也清理缓存
|
|
|
|
|
await cleanupImagePickerCache(); |
|
|
|
|
} |
|
|
|
|
}, 500); |
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
// 处理相机拍照 - 简化版本,不再需要处理galleryUsed
|
|
|
|
|
const handleTakePhoto = useCallback(async () => { |
|
|
|
|
console.log('handleTakePhoto'); |
|
|
|
|
setShowImagePickerModal(false); |
|
|
|
|
|
|
|
|
|
// 等待模态窗关闭后再执行
|
|
|
|
|
setTimeout(async () => { |
|
|
|
|
try { |
|
|
|
|
const permissionResult = await ImagePicker.requestCameraPermissionsAsync(); |
|
|
|
|
if (permissionResult.status !== 'granted') { |
|
|
|
|
console.log('相机权限被拒绝'); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const result = await ImagePicker.launchCameraAsync({ |
|
|
|
|
mediaTypes: ImagePicker.MediaTypeOptions.Images, |
|
|
|
|
allowsEditing: true, |
|
|
|
|
aspect: [4, 3], |
|
|
|
|
quality: 1, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
console.log(result); |
|
|
|
|
|
|
|
|
|
if (!result.canceled && result.assets && result.assets.length > 0) { |
|
|
|
|
console.log('拍照成功:', result.assets[0].uri); |
|
|
|
|
// 这里可以添加后续处理代码,如图片上传等
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 使用后清理缓存
|
|
|
|
|
await cleanupImagePickerCache(); |
|
|
|
|
} catch (error: any) { |
|
|
|
|
console.error('相机错误:', error); |
|
|
|
|
// 出错时也清理缓存
|
|
|
|
|
await cleanupImagePickerCache(); |
|
|
|
|
} |
|
|
|
|
}, 500); |
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
// 重置应用状态函数
|
|
|
|
|
const resetAppState = useCallback(() => { |
|
|
|
|
// 重置标记
|
|
|
|
|
setGalleryUsed(false); |
|
|
|
|
|
|
|
|
|
// 清理缓存
|
|
|
|
|
cleanupImagePickerCache(); |
|
|
|
|
|
|
|
|
|
// 提示用户
|
|
|
|
|
Alert.alert('已重置', '现在您可以使用相机功能了'); |
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
// 渲染列表头部内容
|
|
|
|
|
const renderHeader = () => ( |
|
|
|
|
<> |
|
|
|
@ -495,7 +695,10 @@ export const HomeScreen = () => {
|
|
|
|
|
> |
|
|
|
|
<IconComponent name="search-outline" size={20} color="#999" /> |
|
|
|
|
<Text style={styles.searchPlaceholder}>搜索商品</Text> |
|
|
|
|
<TouchableOpacity style={styles.cameraButton}> |
|
|
|
|
<TouchableOpacity
|
|
|
|
|
style={styles.cameraButton} |
|
|
|
|
onPress={() => setShowImagePickerModal(true)} |
|
|
|
|
> |
|
|
|
|
<IconComponent name="camera-outline" size={24} color="#333" /> |
|
|
|
|
</TouchableOpacity> |
|
|
|
|
</TouchableOpacity> |
|
|
|
@ -710,12 +913,65 @@ export const HomeScreen = () => {
|
|
|
|
|
</View> |
|
|
|
|
</View> |
|
|
|
|
</Modal> |
|
|
|
|
|
|
|
|
|
{/* Image Picker Modal */} |
|
|
|
|
<Modal |
|
|
|
|
visible={showImagePickerModal} |
|
|
|
|
animationType="slide" |
|
|
|
|
transparent={true} |
|
|
|
|
onRequestClose={() => setShowImagePickerModal(false)} |
|
|
|
|
> |
|
|
|
|
<TouchableOpacity
|
|
|
|
|
style={styles.imagePickerOverlay} |
|
|
|
|
activeOpacity={1} |
|
|
|
|
onPress={() => setShowImagePickerModal(false)} |
|
|
|
|
> |
|
|
|
|
<View style={styles.imagePickerContent}> |
|
|
|
|
{!galleryUsed ? ( |
|
|
|
|
// 正常状态,显示相机选项
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
style={styles.imagePickerOption} |
|
|
|
|
onPress={handleTakePhoto} |
|
|
|
|
> |
|
|
|
|
<IconComponent name="camera-outline" size={24} color="#333" /> |
|
|
|
|
<Text style={styles.imagePickerText}>拍照</Text> |
|
|
|
|
</TouchableOpacity> |
|
|
|
|
) : ( |
|
|
|
|
// 已使用相册状态,显示重置选项
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
style={styles.imagePickerOption} |
|
|
|
|
onPress={resetAppState} |
|
|
|
|
> |
|
|
|
|
<IconComponent name="refresh-outline" size={24} color="#333" /> |
|
|
|
|
<Text style={styles.imagePickerText}>重置相机功能</Text> |
|
|
|
|
</TouchableOpacity> |
|
|
|
|
)} |
|
|
|
|
|
|
|
|
|
<View style={styles.imagePickerDivider} /> |
|
|
|
|
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
style={styles.imagePickerOption} |
|
|
|
|
onPress={handleChooseFromGallery} |
|
|
|
|
> |
|
|
|
|
<IconComponent name="images-outline" size={24} color="#333" /> |
|
|
|
|
<Text style={styles.imagePickerText}>从相册选择</Text> |
|
|
|
|
</TouchableOpacity> |
|
|
|
|
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
style={styles.imagePickerCancelButton} |
|
|
|
|
onPress={() => setShowImagePickerModal(false)} |
|
|
|
|
> |
|
|
|
|
<Text style={styles.imagePickerCancelText}>取消</Text> |
|
|
|
|
</TouchableOpacity> |
|
|
|
|
</View> |
|
|
|
|
</TouchableOpacity> |
|
|
|
|
</Modal> |
|
|
|
|
</View> |
|
|
|
|
</View> |
|
|
|
|
</SafeAreaView> |
|
|
|
|
); |
|
|
|
|
}; |
|
|
|
|
const styles = StyleSheet.create({ |
|
|
|
|
const styles = StyleSheet.create<StylesType>({ |
|
|
|
|
safeArea: { |
|
|
|
|
flex: 1, |
|
|
|
|
backgroundColor: '#fff', |
|
|
|
@ -1013,8 +1269,8 @@ const styles = StyleSheet.create({
|
|
|
|
|
zIndex: 2, |
|
|
|
|
}, |
|
|
|
|
vipButton: { |
|
|
|
|
width: widthUtils(30, 66).width, |
|
|
|
|
height: widthUtils(30, 66).height, |
|
|
|
|
width: widthUtils(30, 60).width, |
|
|
|
|
height: widthUtils(30, 60).height, |
|
|
|
|
justifyContent: "center", |
|
|
|
|
alignItems: "center", |
|
|
|
|
backgroundColor: "#3b3b3b", |
|
|
|
@ -1157,4 +1413,43 @@ const styles = StyleSheet.create({
|
|
|
|
|
height: "100%", |
|
|
|
|
borderRadius: 10, |
|
|
|
|
}, |
|
|
|
|
// Image Picker Modal Styles
|
|
|
|
|
imagePickerOverlay: { |
|
|
|
|
flex: 1, |
|
|
|
|
backgroundColor: 'rgba(0, 0, 0, 0.5)', |
|
|
|
|
justifyContent: 'flex-end', |
|
|
|
|
}, |
|
|
|
|
imagePickerContent: { |
|
|
|
|
backgroundColor: '#fff', |
|
|
|
|
borderTopLeftRadius: 20, |
|
|
|
|
borderTopRightRadius: 20, |
|
|
|
|
paddingTop: 20, |
|
|
|
|
}, |
|
|
|
|
imagePickerOption: { |
|
|
|
|
flexDirection: 'row', |
|
|
|
|
alignItems: 'center', |
|
|
|
|
paddingVertical: 16, |
|
|
|
|
paddingHorizontal: 20, |
|
|
|
|
}, |
|
|
|
|
imagePickerText: { |
|
|
|
|
fontSize: fontSize(16), |
|
|
|
|
marginLeft: 12, |
|
|
|
|
color: '#333', |
|
|
|
|
}, |
|
|
|
|
imagePickerDivider: { |
|
|
|
|
height: 1, |
|
|
|
|
backgroundColor: '#f0f0f0', |
|
|
|
|
marginHorizontal: 20, |
|
|
|
|
}, |
|
|
|
|
imagePickerCancelButton: { |
|
|
|
|
alignItems: 'center', |
|
|
|
|
paddingVertical: 16, |
|
|
|
|
marginTop: 8, |
|
|
|
|
borderTopWidth: 1, |
|
|
|
|
borderTopColor: '#f0f0f0', |
|
|
|
|
}, |
|
|
|
|
imagePickerCancelText: { |
|
|
|
|
fontSize: fontSize(16), |
|
|
|
|
color: '#999', |
|
|
|
|
}, |
|
|
|
|
}); |