refactor(ui): enhance TradeDetailScreen with responsive design and image gallery
- Add responsive hooks (useResponsive, useResponsiveValue, useResponsiveSpacing) - Replace custom image handling with ImageGrid and ImageGallery components - Update styling with modern typography and spacing adjustments - Add image modal for fullscreen gallery view
This commit is contained in:
@@ -1,22 +1,23 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { View, ScrollView, Image, StyleSheet, Pressable, Alert, Modal } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { useAppColors, spacing, borderRadius, fontSizes, shadows, type AppColors } from '../../theme';
|
||||
import { Text, Loading, AppBackButton } from '../../components/common';
|
||||
import { Text, Loading, AppBackButton, ImageGrid, ImageGallery } from '../../components/common';
|
||||
import { tradeService } from '../../services/trade/tradeService';
|
||||
import { messageService } from '../../services/message/messageService';
|
||||
import { useIsAuthenticated, useCurrentUser } from '../../stores/auth';
|
||||
import type { TradeItemDetailDTO } from '../../types/trade';
|
||||
import { TRADE_CONDITION_MAP } from '../../types/trade';
|
||||
import { hrefChat, hrefEditTrade } from '../../navigation/hrefs';
|
||||
import { useResponsive, useResponsiveValue, useResponsiveSpacing } from '../../hooks/useResponsive';
|
||||
|
||||
function createStyles(colors: AppColors) {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.background.paper,
|
||||
backgroundColor: colors.background.default,
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
@@ -39,17 +40,6 @@ function createStyles(colors: AppColors) {
|
||||
headerRight: {
|
||||
width: 40,
|
||||
},
|
||||
imageContainer: {
|
||||
width: '100%',
|
||||
aspectRatio: 1,
|
||||
backgroundColor: colors.background.paper,
|
||||
position: 'relative',
|
||||
},
|
||||
image: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
resizeMode: 'contain',
|
||||
},
|
||||
// 降价标签
|
||||
discountBadge: {
|
||||
position: 'absolute',
|
||||
@@ -130,7 +120,7 @@ function createStyles(colors: AppColors) {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
gap: spacing.sm,
|
||||
marginBottom: spacing.md,
|
||||
marginBottom: spacing.lg,
|
||||
},
|
||||
conditionBadge: {
|
||||
paddingHorizontal: 8,
|
||||
@@ -189,10 +179,9 @@ function createStyles(colors: AppColors) {
|
||||
color: '#FF7875',
|
||||
},
|
||||
title: {
|
||||
fontSize: fontSizes['3xl'],
|
||||
fontWeight: '600',
|
||||
fontWeight: '800',
|
||||
color: colors.text.primary,
|
||||
lineHeight: 34,
|
||||
letterSpacing: -0.3,
|
||||
flex: 1,
|
||||
},
|
||||
// 商品属性行
|
||||
@@ -217,12 +206,11 @@ function createStyles(colors: AppColors) {
|
||||
color: colors.text.primary,
|
||||
fontWeight: '500',
|
||||
},
|
||||
// 描述
|
||||
// 描述 - 与帖子正文同步
|
||||
contentText: {
|
||||
fontSize: fontSizes.lg,
|
||||
color: colors.text.primary,
|
||||
lineHeight: 28,
|
||||
marginBottom: spacing.md,
|
||||
letterSpacing: 0.2,
|
||||
marginBottom: spacing.lg,
|
||||
},
|
||||
// 底部操作栏
|
||||
bottomBar: {
|
||||
@@ -362,12 +350,23 @@ export function TradeDetailScreen({ tradeId }: TradeDetailScreenProps) {
|
||||
|
||||
const [item, setItem] = useState<TradeItemDetailDTO | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [currentImageIndex, setCurrentImageIndex] = useState(0);
|
||||
const [manageModalVisible, setManageModalVisible] = useState(false);
|
||||
const [showImageModal, setShowImageModal] = useState(false);
|
||||
const [selectedImageIndex, setSelectedImageIndex] = useState(0);
|
||||
|
||||
const isOwner = item?.user_id === currentUser?.id;
|
||||
|
||||
// 响应式 hook(与帖子同步)
|
||||
const {
|
||||
width,
|
||||
isMobile,
|
||||
isTablet,
|
||||
isDesktop,
|
||||
isWideScreen,
|
||||
} = useResponsive();
|
||||
|
||||
const responsivePadding = useResponsiveSpacing({ xs: 12, sm: 14, md: 16, lg: 20, xl: 24, '2xl': 28, '3xl': 32, '4xl': 40 });
|
||||
const responsiveGap = useResponsiveSpacing({ xs: 8, sm: 10, md: 12, lg: 16, xl: 20, '2xl': 24, '3xl': 28, '4xl': 32 });
|
||||
|
||||
const fetchItem = useCallback(async () => {
|
||||
try {
|
||||
@@ -456,6 +455,28 @@ export function TradeDetailScreen({ tradeId }: TradeDetailScreenProps) {
|
||||
}
|
||||
}, [isAuth, item?.author?.id, router]);
|
||||
|
||||
// 响应式图片间距和圆角(与帖子同步)
|
||||
const imageGap = isDesktop ? 12 : isTablet ? 10 : 8;
|
||||
const imageBorderRadius = isDesktop ? borderRadius.xl : borderRadius.lg;
|
||||
|
||||
const images = item?.images || [];
|
||||
|
||||
// 转换图片格式给 ImageGrid 使用
|
||||
const tradeImages = useMemo(() => {
|
||||
return images.map(img => ({
|
||||
id: img.id,
|
||||
url: img.url || img.preview_url_large || img.preview_url || '',
|
||||
thumbnail_url: img.preview_url || img.preview_url_large || img.url || '',
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
}));
|
||||
}, [images]);
|
||||
|
||||
const handleImagePress = useCallback((allImages: any[], index: number) => {
|
||||
setSelectedImageIndex(index);
|
||||
setShowImageModal(true);
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return <Loading fullScreen />;
|
||||
}
|
||||
@@ -470,9 +491,6 @@ export function TradeDetailScreen({ tradeId }: TradeDetailScreenProps) {
|
||||
);
|
||||
}
|
||||
|
||||
const images = item.images || [];
|
||||
const currentImage = images[currentImageIndex];
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||
<View style={styles.header}>
|
||||
@@ -484,7 +502,7 @@ export function TradeDetailScreen({ tradeId }: TradeDetailScreenProps) {
|
||||
</View>
|
||||
|
||||
<ScrollView>
|
||||
<View style={styles.content}>
|
||||
<View style={[styles.content, { paddingHorizontal: responsivePadding, paddingTop: responsivePadding }]}>
|
||||
{/* 卖家信息头部 */}
|
||||
{item.author && (
|
||||
<View>
|
||||
@@ -522,34 +540,56 @@ export function TradeDetailScreen({ tradeId }: TradeDetailScreenProps) {
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 标题 */}
|
||||
{/* 标题 - 与帖子同步 */}
|
||||
<View style={styles.titleRow}>
|
||||
<Text style={styles.title} numberOfLines={2}>{item.title}</Text>
|
||||
<Text
|
||||
style={[
|
||||
styles.title,
|
||||
{
|
||||
fontSize: isDesktop ? fontSizes['3xl'] : isTablet ? fontSizes['2xl'] : fontSizes['2xl'],
|
||||
lineHeight: isDesktop ? 40 : isTablet ? 36 : 32,
|
||||
},
|
||||
]}
|
||||
numberOfLines={2}
|
||||
>
|
||||
{item.title}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* 商品描述 */}
|
||||
{/* 商品描述 - 与帖子正文同步 */}
|
||||
{item.content && (
|
||||
<Text style={styles.contentText}>{item.content}</Text>
|
||||
<Text
|
||||
style={[
|
||||
styles.contentText,
|
||||
{
|
||||
fontSize: isDesktop ? fontSizes.xl : isTablet ? fontSizes.lg : fontSizes.lg,
|
||||
lineHeight: isDesktop ? 32 : isTablet ? 30 : 28,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.content}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 图片放在文字下方 */}
|
||||
{images.length > 0 && (
|
||||
<View style={{ paddingHorizontal: spacing.md, paddingBottom: spacing.md }}>
|
||||
{images.map((img, idx) => (
|
||||
<View key={img.id || idx} style={[styles.imageContainer, { marginBottom: spacing.md, borderRadius: borderRadius.lg, overflow: 'hidden' }]}>
|
||||
<Image
|
||||
source={{ uri: img.url || img.preview_url_large || img.preview_url || '' }}
|
||||
style={styles.image}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
{/* 图片 - 使用 ImageGrid 与帖子同步 */}
|
||||
{tradeImages.length > 0 && (
|
||||
<View style={{ paddingHorizontal: responsivePadding, paddingBottom: responsivePadding }}>
|
||||
<ImageGrid
|
||||
images={tradeImages}
|
||||
mode="auto"
|
||||
gap={imageGap}
|
||||
borderRadius={imageBorderRadius}
|
||||
maxDisplayCount={isWideScreen ? 20 : 100}
|
||||
showMoreOverlay={false}
|
||||
onImagePress={handleImagePress}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 内容区右下角属性文字(仅出售商品显示) */}
|
||||
{item.trade_type === 'sell' && (
|
||||
<View style={{ paddingHorizontal: spacing.md, paddingBottom: spacing.md }}>
|
||||
<View style={{ paddingHorizontal: responsivePadding, paddingBottom: responsivePadding }}>
|
||||
<View style={styles.contentAttrs}>
|
||||
<Text style={styles.contentAttrText}>品牌:{item.brand || '其他'}</Text>
|
||||
<Text style={styles.contentAttrText}>
|
||||
@@ -617,6 +657,18 @@ export function TradeDetailScreen({ tradeId }: TradeDetailScreenProps) {
|
||||
</View>
|
||||
</Pressable>
|
||||
</Modal>
|
||||
|
||||
{/* 图片预览 */}
|
||||
<ImageGallery
|
||||
visible={showImageModal}
|
||||
images={tradeImages.map(img => ({
|
||||
id: img.id || img.url || String(Math.random()),
|
||||
url: img.url || '',
|
||||
}))}
|
||||
initialIndex={selectedImageIndex}
|
||||
onClose={() => setShowImageModal(false)}
|
||||
enableSave
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user