Mac 2 months ago
parent
commit
e2171b50ea
  1. 58
      app/components/ResponsiveContainer.tsx
  2. 74
      app/components/ResponsiveGrid.tsx
  3. 118
      app/constants/styles.ts
  4. 751
      app/screens/SearchResultScreen.tsx
  5. 45
      app/utils/dimensions.ts
  6. 1
      app/utils/size.ts
  7. 17
      babel.config.js
  8. 35
      package-lock.json
  9. 2
      package.json
  10. 27
      yarn.lock

58
app/components/ResponsiveContainer.tsx

@ -0,0 +1,58 @@
import React from 'react';
import { View, ViewStyle, StyleSheet } from 'react-native';
import { getScreenWidth, isTablet } from '../utils/dimensions';
import { spacing } from '../constants/styles';
interface ResponsiveContainerProps {
children: React.ReactNode;
style?: ViewStyle;
maxWidth?: number;
padding?: 'none' | 'sm' | 'md' | 'lg';
}
export const ResponsiveContainer: React.FC<ResponsiveContainerProps> = ({
children,
style,
maxWidth = 1200,
padding = 'md',
}) => {
const screenWidth = getScreenWidth();
const isTabletDevice = isTablet();
// 计算容器宽度
const containerWidth = Math.min(screenWidth, maxWidth);
// 计算水平内边距
const horizontalPadding = padding === 'none'
? 0
: padding === 'sm'
? spacing.sm
: padding === 'lg'
? spacing.lg
: spacing.md;
return (
<View style={[styles.container, style]}>
<View style={[
styles.content,
{
width: containerWidth,
paddingHorizontal: horizontalPadding,
},
]}>
{children}
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
width: '100%',
alignItems: 'center',
},
content: {
flex: 1,
},
});

74
app/components/ResponsiveGrid.tsx

@ -0,0 +1,74 @@
import React from 'react';
import { View, ViewStyle, StyleSheet } from 'react-native';
import { getScreenWidth, isTablet } from '../utils/dimensions';
import { spacing } from '../constants/styles';
interface ResponsiveGridProps {
children: React.ReactNode;
style?: ViewStyle;
columns?: number;
gap?: 'none' | 'sm' | 'md' | 'lg';
padding?: 'none' | 'sm' | 'md' | 'lg';
}
export const ResponsiveGrid: React.FC<ResponsiveGridProps> = ({
children,
style,
columns = 2,
gap = 'md',
padding = 'md',
}) => {
const screenWidth = getScreenWidth();
const isTabletDevice = isTablet();
// 根据设备类型调整列数
const adjustedColumns = isTabletDevice ? Math.min(columns * 1.5, 4) : columns;
// 计算间距
const gapSize = gap === 'none'
? 0
: gap === 'sm'
? spacing.sm
: gap === 'lg'
? spacing.lg
: spacing.md;
// 计算内边距
const paddingSize = padding === 'none'
? 0
: padding === 'sm'
? spacing.sm
: padding === 'lg'
? spacing.lg
: spacing.md;
// 计算每个项目的宽度
const itemWidth = (screenWidth - (paddingSize * 2) - (gapSize * (adjustedColumns - 1))) / adjustedColumns;
// 将子元素转换为数组
const childrenArray = React.Children.toArray(children);
return (
<View style={[styles.container, { padding: paddingSize }, style]}>
<View style={[styles.grid, { gap: gapSize }]}>
{childrenArray.map((child, index) => (
<View key={index} style={{ width: itemWidth }}>
{child}
</View>
))}
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
width: '100%',
},
grid: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'flex-start',
},
});

118
app/constants/styles.ts

@ -0,0 +1,118 @@
import { StyleSheet } from 'react-native';
import { scale, verticalScale, moderateScale } from '../utils/dimensions';
export const globalStyles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#FFFFFF',
},
safeArea: {
flex: 1,
},
row: {
flexDirection: 'row',
},
column: {
flexDirection: 'column',
},
center: {
justifyContent: 'center',
alignItems: 'center',
},
spaceBetween: {
justifyContent: 'space-between',
},
spaceAround: {
justifyContent: 'space-around',
},
flexStart: {
justifyContent: 'flex-start',
},
flexEnd: {
justifyContent: 'flex-end',
},
});
export const spacing = {
xs: scale(4),
sm: scale(8),
md: scale(16),
lg: scale(24),
xl: scale(32),
xxl: scale(40),
};
export const fontSize = {
xs: moderateScale(12),
sm: moderateScale(14),
md: moderateScale(16),
lg: moderateScale(18),
xl: moderateScale(20),
xxl: moderateScale(24),
};
export const borderRadius = {
sm: scale(4),
md: scale(8),
lg: scale(12),
xl: scale(16),
round: scale(9999),
};
export const iconSize = {
sm: scale(16),
md: scale(24),
lg: scale(32),
xl: scale(40),
};
export const buttonHeight = {
sm: verticalScale(32),
md: verticalScale(44),
lg: verticalScale(56),
};
export const inputHeight = {
sm: verticalScale(32),
md: verticalScale(44),
lg: verticalScale(56),
};
export const cardPadding = {
sm: scale(8),
md: scale(16),
lg: scale(24),
};
export const shadow = {
small: {
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 2,
},
medium: {
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 4,
},
shadowOpacity: 0.30,
shadowRadius: 4.65,
elevation: 4,
},
large: {
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 6,
},
shadowOpacity: 0.37,
shadowRadius: 7.49,
elevation: 6,
},
};

751
app/screens/SearchResultScreen.tsx

@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback } from "react"; import React, { useState, useEffect, useCallback, useRef } from "react";
import { import {
View, View,
Text, Text,
@ -12,7 +12,6 @@ import {
ActivityIndicator, ActivityIndicator,
KeyboardAvoidingView, KeyboardAvoidingView,
Platform, Platform,
Keyboard,
ScrollView, ScrollView,
} from "react-native"; } from "react-native";
import Ionicons from "@expo/vector-icons/Ionicons"; import Ionicons from "@expo/vector-icons/Ionicons";
@ -39,6 +38,56 @@ type SearchResultRouteParams = {
keyword: string; keyword: string;
}; };
// 懒加载图片组件
const LazyImage = React.memo(({ uri, style, resizeMode }: { uri: string, style: any, resizeMode: any }) => {
const [isVisible, setIsVisible] = useState(false);
const [hasError, setHasError] = useState(false);
// // 缩略图处理 - 为原图创建更低质量的缩略版本以更快加载
// const getThumbnailUrl = useCallback((originalUrl: string) => {
// // 如果有可能,使用CDN参数来获取更小的图片
// // 这里是一个简单的实现,实际上需要根据具体的CDN服务来调整
// if (originalUrl.includes('?')) {
// return `${originalUrl}&quality=10&width=100`;
// }
// return `${originalUrl}?quality=10&width=100`;
// }, []);
const onError = useCallback(() => {
setHasError(true);
}, []);
// 使用IntersectionObserver的替代方案,在组件挂载时显示图片
useEffect(() => {
// 延迟一小段时间后开始加载图片
const timer = setTimeout(() => {
setIsVisible(true);
}, 100);
return () => clearTimeout(timer);
}, []);
return (
<View style={[style, { backgroundColor: '#f9f9f9', overflow: 'hidden' }]}>
{hasError && (
<View style={[style, { justifyContent: 'center', alignItems: 'center', backgroundColor: '#f5f5f5' }]}>
<IconComponent name="image-outline" size={24} color="#999" />
<Text style={{ fontSize: 12, color: '#999', marginTop: 4 }}></Text>
</View>
)}
{isVisible && !hasError && (
<Image
source={{ uri }}
style={style}
resizeMode={resizeMode}
onError={onError}
/>
)}
</View>
);
});
// 产品项组件 - 使用React.memo优化渲染 // 产品项组件 - 使用React.memo优化渲染
const ProductItem = React.memo( const ProductItem = React.memo(
({ ({
@ -57,8 +106,8 @@ const ProductItem = React.memo(
> >
<View style={styles.productImageContainer}> <View style={styles.productImageContainer}>
{product.product_image_urls[0] ? ( {product.product_image_urls[0] ? (
<Image <LazyImage
source={{ uri: product.product_image_urls[0] }} uri={product.product_image_urls[0]}
style={styles.productImage} style={styles.productImage}
resizeMode="cover" resizeMode="cover"
/> />
@ -66,15 +115,20 @@ const ProductItem = React.memo(
<Text style={styles.placeholderText}>product picture</Text> <Text style={styles.placeholderText}>product picture</Text>
)} )}
</View> </View>
{/* 价格 */} {/* 产品分类 */}
<View style={styles.productInfo}> <View style={styles.productInfo}>
<Text style={styles.productPrice}> <Text style={styles.categoryText} numberOfLines={2}>
{product?.min_price?.toFixed(2)} {product.subject_trans}
</Text>
{/* 产品标题 */}
<Text style={styles.productTitle} numberOfLines={2}>
{product.subject}
</Text> </Text>
{/* 价格信息 */}
<View style={styles.priceRow}>
<Text style={styles.currentPrice}>
{product?.min_price?.toFixed(0)} <Text style={styles.currency}>FCFA</Text>
</Text>
<Text style={styles.originalPrice}>
3000<Text style={styles.currencySmall}>FCFA</Text>
</Text>
</View>
{/* 销售量 */} {/* 销售量 */}
<Text style={styles.productSales}> <Text style={styles.productSales}>
{t('monthlySales')}: {product.sold_out} {t('monthlySales')}: {product.sold_out}
@ -91,24 +145,22 @@ export const SearchResultScreen = () => {
useRoute<RouteProp<Record<string, SearchResultRouteParams>, string>>(); useRoute<RouteProp<Record<string, SearchResultRouteParams>, string>>();
const [searchText, setSearchText] = useState(""); const [searchText, setSearchText] = useState("");
const [products, setProducts] = useState<Product[]>([]); const [products, setProducts] = useState<Product[]>([]);
const [originalProducts, setOriginalProducts] = useState<Product[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [hasMore, setHasMore] = useState(true); const [hasMore, setHasMore] = useState(true);
const [loadingMore, setLoadingMore] = useState(false); const [loadingMore, setLoadingMore] = useState(false);
const [minPrice, setMinPrice] = useState<string>("");
const [maxPrice, setMaxPrice] = useState<string>("");
const [isFilterVisible, setIsFilterVisible] = useState(false); const [isFilterVisible, setIsFilterVisible] = useState(false);
const [sortOrder, setSortOrder] = useState<"asc" | "desc" | null>(null); const [sortOrder, setSortOrder] = useState<"asc" | "desc" | null>(null);
const [sortField, setSortField] = useState<"price" | "time">("price"); const [sortField, setSortField] = useState<"price" | "time">("price");
const [showBackToTop, setShowBackToTop] = useState(false); const [showBackToTop, setShowBackToTop] = useState(false);
const flatListRef = React.useRef<FlatList>(null); const flatListRef = useRef<FlatList>(null);
const [activeTab, setActiveTab] = useState<"default" | "volume" | "price">("default");
const [searchParams, setSearchParams] = useState<ProductParams>({ const [searchParams, setSearchParams] = useState<ProductParams>({
keyword: route.params?.keyword || "", keyword: route.params?.keyword || "",
page: 1, page: 1,
page_size: 20, page_size: 20,
sort_order: "desc", sort_order: "desc",
max_price: null,
min_price: null,
category_id: null, category_id: null,
sort_by: "create_date", sort_by: "create_date",
}); });
@ -136,6 +188,8 @@ export const SearchResultScreen = () => {
setProducts((prev) => [...prev, ...res.products]); setProducts((prev) => [...prev, ...res.products]);
} else { } else {
setProducts(res.products); setProducts(res.products);
// 保存原始排序的数据,以便默认排序时恢复
setOriginalProducts(res.products);
} }
// 如果返回的数据少于页面大小,说明没有更多数据了 // 如果返回的数据少于页面大小,说明没有更多数据了
setHasMore(res.products.length === params.page_size); setHasMore(res.products.length === params.page_size);
@ -155,6 +209,8 @@ export const SearchResultScreen = () => {
// 重置排序状态 // 重置排序状态
setSortField("price"); setSortField("price");
setSortOrder(null); setSortOrder(null);
// 重置到默认标签
setActiveTab("default");
const newParams = { const newParams = {
...searchParams, ...searchParams,
@ -166,43 +222,6 @@ export const SearchResultScreen = () => {
} }
}, [searchText, searchParams, searchProducts]); }, [searchText, searchParams, searchProducts]);
// 处理价格筛选
const handlePriceFilter = useCallback(() => {
Keyboard.dismiss();
const newParams = { ...searchParams };
if (minPrice.trim()) {
newParams.min_price = parseFloat(minPrice);
} else {
newParams.min_price = null;
}
if (maxPrice.trim()) {
newParams.max_price = parseFloat(maxPrice);
} else {
newParams.max_price = null;
}
newParams.page = 1; // 重置到第一页
setSearchParams(newParams);
searchProducts(newParams);
console.log(newParams);
}, [minPrice, maxPrice, searchParams, searchProducts]);
// 重置价格筛选
const resetPriceFilter = useCallback(() => {
setMinPrice("");
setMaxPrice("");
const newParams = {
...searchParams,
min_price: null,
max_price: null,
page: 1,
};
setSearchParams(newParams);
searchProducts(newParams);
}, [searchParams, searchProducts]);
// 切换筛选器显示状态 // 切换筛选器显示状态
const toggleFilter = useCallback(() => { const toggleFilter = useCallback(() => {
setIsFilterVisible(!isFilterVisible); setIsFilterVisible(!isFilterVisible);
@ -250,6 +269,9 @@ export const SearchResultScreen = () => {
[handleProductPress, t] [handleProductPress, t]
); );
// 创建产品列表项的key提取器
const keyExtractor = useCallback((item: Product) => String(item.offer_id), []);
// 处理排序 // 处理排序
const handleSort = useCallback( const handleSort = useCallback(
(field: "price" | "time", order: "asc" | "desc") => { (field: "price" | "time", order: "asc" | "desc") => {
@ -327,6 +349,40 @@ export const SearchResultScreen = () => {
flatListRef.current?.scrollToOffset({ offset: 0, animated: true }); flatListRef.current?.scrollToOffset({ offset: 0, animated: true });
}, []); }, []);
// 处理标签切换
const handleTabChange = useCallback((tab: "default" | "volume" | "price") => {
// 如果点击的是已经激活的价格标签,则切换排序顺序
if (tab === "price" && activeTab === "price") {
// 如果当前是价格升序,则切换为降序;如果是降序或未设置,则切换为升序
const newOrder = sortOrder === "asc" ? "desc" : "asc";
handleSort("price", newOrder);
scrollToTop();
} else {
setActiveTab(tab);
// 根据标签类型设置排序规则
if (tab === "price") {
// 默认价格从低到高
handleSort("price", "asc");
scrollToTop();
} else if (tab === "volume") {
// 按销量排序
const sortedProducts = [...originalProducts];
sortedProducts.sort((a, b) => {
const volumeA = a.sold_out || 0;
const volumeB = b.sold_out || 0;
return volumeB - volumeA; // 从高到低排序
});
setProducts(sortedProducts);
scrollToTop();
} else {
// 默认排序 - 恢复到原始数据顺序
setProducts([...originalProducts]);
scrollToTop();
}
}
}, [handleSort, activeTab, sortOrder, originalProducts, scrollToTop]);
return ( return (
<SafeAreaView style={styles.safeArea}> <SafeAreaView style={styles.safeArea}>
<StatusBar barStyle="dark-content" backgroundColor="#fff" /> <StatusBar barStyle="dark-content" backgroundColor="#fff" />
@ -340,7 +396,7 @@ export const SearchResultScreen = () => {
<IconComponent name="arrow-back" size={24} color="#333" /> <IconComponent name="arrow-back" size={24} color="#333" />
</TouchableOpacity> </TouchableOpacity>
<View style={styles.searchBar}> <View style={styles.searchBar}>
<IconComponent name="search-outline" size={20} color="#999" /> <IconComponent name="search-outline" size={18} color="#999" />
<TextInput <TextInput
style={styles.searchInput} style={styles.searchInput}
placeholder={t('searchProducts')} placeholder={t('searchProducts')}
@ -351,196 +407,192 @@ export const SearchResultScreen = () => {
onSubmitEditing={handleSearch} onSubmitEditing={handleSearch}
/> />
{searchText.length > 0 && ( {searchText.length > 0 && (
<TouchableOpacity onPress={() => setSearchText("")}> <TouchableOpacity
<IconComponent name="close-circle" size={20} color="#999" /> onPress={() => setSearchText("")}
style={styles.clearButton}
>
<IconComponent name="close-circle" size={18} color="#999" />
</TouchableOpacity> </TouchableOpacity>
)} )}
</View> </View>
<TouchableOpacity style={styles.filterButton} onPress={toggleFilter}> <TouchableOpacity style={styles.searchButton} onPress={handleSearch}>
<IconComponent <Text style={styles.searchButtonText}>{t('cancel')}</Text>
name={isFilterVisible ? "options" : "options-outline"}
size={24}
color="#333"
/>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
{/* 搜索结果 */} {/* 标签筛选 */}
<View style={styles.resultsContainer}> <View style={styles.tabContainer}>
{/* 价格筛选器 */} <TouchableOpacity
{isFilterVisible && ( style={[styles.tabButton, activeTab === "default" && styles.activeTabButton]}
<View style={styles.filterContainer}> onPress={() => handleTabChange("default")}
<View style={styles.priceFilterRow}> >
<Text style={styles.filterLabel}>{t('priceRange')}</Text> <Text style={[styles.tabText, activeTab === "default" && styles.activeTabText]}>
<View style={styles.priceInputContainer}> {t('default')}
<TextInput </Text>
style={styles.priceInput} </TouchableOpacity>
placeholder={t('minPrice')} <TouchableOpacity
placeholderTextColor="#999" style={[styles.tabButton, activeTab === "volume" && styles.activeTabButton]}
value={minPrice} onPress={() => handleTabChange("volume")}
onChangeText={setMinPrice} >
keyboardType="numeric" <Text style={[styles.tabText, activeTab === "volume" && styles.activeTabText]}>
returnKeyType="done" {t('volume')}
/> </Text>
<Text style={styles.priceDivider}>-</Text> </TouchableOpacity>
<TextInput <TouchableOpacity
style={styles.priceInput} style={[styles.tabButton, activeTab === "price" && styles.activeTabButton]}
placeholder={t('maxPrice')} onPress={() => handleTabChange("price")}
placeholderTextColor="#999" >
value={maxPrice} <View style={styles.tabButtonContent}>
onChangeText={setMaxPrice} <Text style={[styles.tabText, activeTab === "price" && styles.activeTabText]}>
keyboardType="numeric" {t('price')}
returnKeyType="done" </Text>
{activeTab === "price" && (
<View style={styles.tabIcon}>
<IconComponent
name={sortOrder === "desc" ? "chevron-down" : "chevron-up"}
size={16}
color="#000"
/> />
</View> </View>
</View> )}
<View style={styles.filterButtons}>
<TouchableOpacity
style={styles.resetButton}
onPress={resetPriceFilter}
>
<Text style={styles.resetButtonText}>{t('reset')}</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.applyButton}
onPress={handlePriceFilter}
>
<Text style={styles.applyButtonText}>{t('apply')}</Text>
</TouchableOpacity>
</View>
</View> </View>
)} </TouchableOpacity>
</View>
{/* 搜索结果 */}
<View style={styles.resultsContainer}>
{/* 搜索结果标题栏和排序选项 */} {/* 搜索结果标题栏和排序选项 */}
<View style={styles.resultsHeader}> {isFilterVisible && (
<ScrollView <View style={styles.resultsHeader}>
horizontal <ScrollView
showsHorizontalScrollIndicator={false} horizontal
style={styles.sortScrollView} showsHorizontalScrollIndicator={false}
> style={styles.sortScrollView}
<View style={styles.sortGroup}> >
<Text style={styles.sortLabel}>{t('price')}:</Text> <View style={styles.sortGroup}>
<View style={styles.sortButtons}> <Text style={styles.sortLabel}>{t('price')}:</Text>
<TouchableOpacity <View style={styles.sortButtons}>
style={[ <TouchableOpacity
styles.sortButton,
sortField === "price" && sortOrder === "asc"
? styles.sortButtonActive
: {},
]}
onPress={() => handleSort("price", "asc")}
>
<Text
style={[ style={[
styles.sortButtonText, styles.sortButton,
sortField === "price" && sortOrder === "asc" sortField === "price" && sortOrder === "asc"
? styles.sortButtonTextActive ? styles.sortButtonActive
: {}, : {},
]} ]}
onPress={() => handleSort("price", "asc")}
> >
{t('lowToHigh')} <Text
</Text> style={[
{sortField === "price" && sortOrder === "asc" && ( styles.sortButtonText,
<IconComponent sortField === "price" && sortOrder === "asc"
name="chevron-up" ? styles.sortButtonTextActive
size={16} : {},
color="#ff6600" ]}
/> >
)} {t('lowToHigh')}
</TouchableOpacity> </Text>
<TouchableOpacity {sortField === "price" && sortOrder === "asc" && (
style={[ <IconComponent
styles.sortButton, name="chevron-up"
sortField === "price" && sortOrder === "desc" size={16}
? styles.sortButtonActive color="#ff6600"
: {}, />
]} )}
onPress={() => handleSort("price", "desc")} </TouchableOpacity>
> <TouchableOpacity
<Text
style={[ style={[
styles.sortButtonText, styles.sortButton,
sortField === "price" && sortOrder === "desc" sortField === "price" && sortOrder === "desc"
? styles.sortButtonTextActive ? styles.sortButtonActive
: {}, : {},
]} ]}
onPress={() => handleSort("price", "desc")}
> >
{t('highToLow')} <Text
</Text> style={[
{sortField === "price" && sortOrder === "desc" && ( styles.sortButtonText,
<IconComponent sortField === "price" && sortOrder === "desc"
name="chevron-down" ? styles.sortButtonTextActive
size={16} : {},
color="#ff6600" ]}
/> >
)} {t('highToLow')}
</TouchableOpacity> </Text>
{sortField === "price" && sortOrder === "desc" && (
<IconComponent
name="chevron-down"
size={16}
color="#ff6600"
/>
)}
</TouchableOpacity>
</View>
</View> </View>
</View>
<View style={styles.sortDivider} />
<View style={styles.sortDivider} />
<View style={styles.sortGroup}>
<View style={styles.sortGroup}> <Text style={styles.sortLabel}>{t('time')}:</Text>
<Text style={styles.sortLabel}>{t('time')}:</Text> <View style={styles.sortButtons}>
<View style={styles.sortButtons}> <TouchableOpacity
<TouchableOpacity
style={[
styles.sortButton,
sortField === "time" && sortOrder === "asc"
? styles.sortButtonActive
: {},
]}
onPress={() => handleSort("time", "asc")}
>
<Text
style={[ style={[
styles.sortButtonText, styles.sortButton,
sortField === "time" && sortOrder === "asc" sortField === "time" && sortOrder === "asc"
? styles.sortButtonTextActive ? styles.sortButtonActive
: {}, : {},
]} ]}
onPress={() => handleSort("time", "asc")}
> >
{t('oldest')} <Text
</Text> style={[
{sortField === "time" && sortOrder === "asc" && ( styles.sortButtonText,
<IconComponent sortField === "time" && sortOrder === "asc"
name="chevron-up" ? styles.sortButtonTextActive
size={16} : {},
color="#ff6600" ]}
/> >
)} {t('oldest')}
</TouchableOpacity> </Text>
<TouchableOpacity {sortField === "time" && sortOrder === "asc" && (
style={[ <IconComponent
styles.sortButton, name="chevron-up"
sortField === "time" && sortOrder === "desc" size={16}
? styles.sortButtonActive color="#ff6600"
: {}, />
]} )}
onPress={() => handleSort("time", "desc")} </TouchableOpacity>
> <TouchableOpacity
<Text
style={[ style={[
styles.sortButtonText, styles.sortButton,
sortField === "time" && sortOrder === "desc" sortField === "time" && sortOrder === "desc"
? styles.sortButtonTextActive ? styles.sortButtonActive
: {}, : {},
]} ]}
onPress={() => handleSort("time", "desc")}
> >
{t('newest')} <Text
</Text> style={[
{sortField === "time" && sortOrder === "desc" && ( styles.sortButtonText,
<IconComponent sortField === "time" && sortOrder === "desc"
name="chevron-down" ? styles.sortButtonTextActive
size={16} : {},
color="#ff6600" ]}
/> >
)} {t('newest')}
</TouchableOpacity> </Text>
{sortField === "time" && sortOrder === "desc" && (
<IconComponent
name="chevron-down"
size={16}
color="#ff6600"
/>
)}
</TouchableOpacity>
</View>
</View> </View>
</View> </ScrollView>
</ScrollView> </View>
</View> )}
{/* 加载指示器 */} {/* 加载指示器 */}
{loading ? ( {loading ? (
@ -553,18 +605,19 @@ export const SearchResultScreen = () => {
ref={flatListRef} ref={flatListRef}
data={products} data={products}
renderItem={renderProductItem} renderItem={renderProductItem}
keyExtractor={(item) => String(item.offer_id)} keyExtractor={keyExtractor}
numColumns={2} numColumns={2}
contentContainerStyle={styles.productGrid} contentContainerStyle={styles.productGrid}
ListEmptyComponent={renderEmptyList} ListEmptyComponent={renderEmptyList}
ListFooterComponent={renderFooter} ListFooterComponent={renderFooter}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
initialNumToRender={8} initialNumToRender={4}
maxToRenderPerBatch={10} maxToRenderPerBatch={8}
windowSize={5} windowSize={3}
removeClippedSubviews={true} removeClippedSubviews={Platform.OS !== 'web'}
updateCellsBatchingPeriod={50}
onEndReached={handleLoadMore} onEndReached={handleLoadMore}
onEndReachedThreshold={0.2} onEndReachedThreshold={0.5}
onScroll={handleScroll} onScroll={handleScroll}
scrollEventThrottle={16} scrollEventThrottle={16}
/> />
@ -597,15 +650,14 @@ const styles = StyleSheet.create({
searchHeader: { searchHeader: {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
paddingHorizontal: 15, paddingHorizontal: 12,
paddingVertical: 10, paddingVertical: 8,
backgroundColor: "#fff", backgroundColor: "#fff",
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: "#f0f0f0", borderBottomColor: "#f0f0f0",
}, },
backButton: { backButton: {
marginRight: 10, padding: 4,
padding: 5,
}, },
searchBar: { searchBar: {
flex: 1, flex: 1,
@ -613,34 +665,75 @@ const styles = StyleSheet.create({
alignItems: "center", alignItems: "center",
backgroundColor: "#f5f5f5", backgroundColor: "#f5f5f5",
borderRadius: 20, borderRadius: 20,
paddingHorizontal: 15, paddingHorizontal: 8,
height: 40, height: 40,
marginHorizontal: 8,
position: "relative",
}, },
searchInput: { searchInput: {
flex: 1, flex: 1,
marginLeft: 8, marginLeft: 4,
fontSize: 16, fontSize: 16,
color: "#333", color: "#333",
height: 40, height: 40,
paddingRight: 32,
}, },
resultsContainer: { clearButton: {
flex: 1, position: "absolute",
right: 8,
top: "50%",
transform: [{ translateY: -9 }],
padding: 4,
}, },
resultsHeader: { searchButton: {
paddingVertical: 4,
paddingHorizontal: 8,
},
searchButtonText: {
fontSize: 16,
color: "#333",
fontWeight: "500",
},
tabContainer: {
flexDirection: "row",
backgroundColor: "#fff", backgroundColor: "#fff",
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: "#f0f0f0", borderBottomColor: "#f0f0f0",
paddingVertical: 10,
}, },
resultsTitle: { tabButton: {
fontSize: 16,
fontWeight: "bold",
color: "#333",
flex: 1, flex: 1,
alignItems: "center",
justifyContent: "center",
paddingVertical: 12,
position: "relative",
}, },
resultsCount: { tabButtonContent: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
},
tabIcon: {
marginLeft: 4,
},
tabText: {
fontSize: 14, fontSize: 14,
color: "#999", color: "#666",
},
activeTabText: {
color: "#000",
fontWeight: "bold",
},
activeTabButton: {
// borderBottomColor: "#007AFF",
},
resultsContainer: {
flex: 1,
},
resultsHeader: {
backgroundColor: "#fff",
borderBottomWidth: 1,
borderBottomColor: "#f0f0f0",
paddingVertical: 8,
}, },
loadingContainer: { loadingContainer: {
flex: 1, flex: 1,
@ -656,11 +749,14 @@ const styles = StyleSheet.create({
backgroundColor: "#fff", backgroundColor: "#fff",
borderRadius: 8, borderRadius: 8,
overflow: "hidden", overflow: "hidden",
elevation: 2, // Android shadow shadowColor: "#000",
shadowColor: "#000", // iOS shadow shadowOffset: {
shadowOffset: { width: 0, height: 1 }, width: 0,
height: 2,
},
shadowOpacity: 0.1, shadowOpacity: 0.1,
shadowRadius: 2, shadowRadius: 4,
elevation: 3,
}, },
productImageContainer: { productImageContainer: {
height: 150, height: 150,
@ -677,107 +773,47 @@ const styles = StyleSheet.create({
fontSize: 16, fontSize: 16,
}, },
productInfo: { productInfo: {
padding: 10, padding: 8,
},
productPrice: {
fontSize: 18,
fontWeight: "bold",
color: "#ff6600",
marginBottom: 5,
},
productTitle: {
fontSize: 14,
color: "#333",
marginBottom: 5,
},
productSales: {
fontSize: 12,
color: "#999",
},
emptyContainer: {
flex: 1,
minHeight: 300,
justifyContent: "center",
alignItems: "center",
padding: 20,
},
emptyText: {
fontSize: 16,
fontWeight: "bold",
color: "#333",
marginTop: 15,
marginBottom: 8,
},
emptySubtext: {
fontSize: 14,
color: "#999",
textAlign: "center",
},
filterButton: {
marginLeft: 10,
padding: 5,
},
filterContainer: {
backgroundColor: "#fff",
paddingHorizontal: 15,
paddingVertical: 10,
borderBottomWidth: 1,
borderBottomColor: "#f0f0f0",
},
priceFilterRow: {
flexDirection: "row",
alignItems: "center",
marginBottom: 10,
}, },
filterLabel: { categoryText: {
fontSize: 14, fontSize: 10,
fontWeight: "bold", color: "#000000",
color: "#333", fontWeight: "600",
marginRight: 10, marginBottom: 4,
fontFamily: 'PingFang SC'
}, },
priceInputContainer: { priceRow: {
flex: 1,
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "baseline",
marginBottom: 4,
}, },
priceInput: { currentPrice: {
flex: 1,
height: 36,
backgroundColor: "#f5f5f5",
borderRadius: 4,
paddingHorizontal: 10,
fontSize: 14, fontSize: 14,
color: "#333", fontWeight: "600",
}, color: "#ff6600",
priceDivider: { marginRight: 4,
marginHorizontal: 8,
color: "#333",
},
filterButtons: {
flexDirection: "row",
justifyContent: "flex-end",
}, },
resetButton: { currency: {
paddingHorizontal: 15, fontSize: 10,
paddingVertical: 6, fontWeight: "600",
borderRadius: 4, fontFamily: 'PingFang SC',
backgroundColor: "#f5f5f5",
marginRight: 10,
}, },
resetButtonText: { originalPrice: {
fontSize: 14, fontSize: 10,
color: "#666", color: "#999",
textDecorationLine: "line-through",
}, },
applyButton: { currencySmall: {
paddingHorizontal: 15, fontSize: 10,
paddingVertical: 6, color: '#9a9a9a',
borderRadius: 4, fontWeight: "600",
backgroundColor: "#0066FF", fontFamily: 'PingFang SC',
}, },
applyButtonText: { productSales: {
fontSize: 14, fontSize: 10,
color: "#fff", fontWeight: "600",
fontWeight: "bold", fontFamily: 'PingFang SC',
color: "#7c7c7c",
}, },
sortScrollView: { sortScrollView: {
flexGrow: 0, flexGrow: 0,
@ -785,7 +821,7 @@ const styles = StyleSheet.create({
sortGroup: { sortGroup: {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
paddingHorizontal: 15, paddingHorizontal: 16,
}, },
sortLabel: { sortLabel: {
fontSize: 14, fontSize: 14,
@ -802,7 +838,7 @@ const styles = StyleSheet.create({
paddingVertical: 4, paddingVertical: 4,
paddingHorizontal: 8, paddingHorizontal: 8,
borderRadius: 4, borderRadius: 4,
marginLeft: 6, marginLeft: 4,
borderWidth: 1, borderWidth: 1,
borderColor: "#e0e0e0", borderColor: "#e0e0e0",
}, },
@ -822,10 +858,10 @@ const styles = StyleSheet.create({
width: 1, width: 1,
height: 20, height: 20,
backgroundColor: "#e0e0e0", backgroundColor: "#e0e0e0",
marginHorizontal: 15, marginHorizontal: 16,
}, },
footerContainer: { footerContainer: {
padding: 15, padding: 16,
alignItems: "center", alignItems: "center",
flexDirection: "row", flexDirection: "row",
justifyContent: "center", justifyContent: "center",
@ -848,10 +884,51 @@ const styles = StyleSheet.create({
backgroundColor: "#0066FF", backgroundColor: "#0066FF",
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
elevation: 5,
shadowColor: "#000", shadowColor: "#000",
shadowOffset: { width: 0, height: 2 }, shadowOffset: {
shadowOpacity: 0.3, width: 0,
shadowRadius: 3, height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
},
emptyContainer: {
flex: 1,
minHeight: 300,
justifyContent: "center",
alignItems: "center",
padding: 16,
},
emptyText: {
fontSize: 16,
fontWeight: "bold",
color: "#333",
marginTop: 16,
marginBottom: 8,
},
emptySubtext: {
fontSize: 14,
color: "#999",
textAlign: "center",
},
resultsTitle: {
fontSize: 16,
fontWeight: "bold",
color: "#333",
flex: 1,
},
resultsCount: {
fontSize: 14,
color: "#999",
},
filterButton: {
marginLeft: 8,
padding: 4,
},
imagePlaceholder: {
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f5f5f5',
}, },
}); });

45
app/utils/dimensions.ts

@ -0,0 +1,45 @@
import { Dimensions, Platform, StatusBar, PixelRatio } from 'react-native';
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
// 基准尺寸(以 iPhone 11 为基准)
const baseWidth = 375;
const baseHeight = 812;
// 计算缩放比例
const widthRatio = SCREEN_WIDTH / baseWidth;
const heightRatio = SCREEN_HEIGHT / baseHeight;
// 获取状态栏高度
const statusBarHeight = Platform.OS === 'ios' ? 44 : StatusBar.currentHeight || 0;
// 响应式尺寸计算函数
export const scale = (size: number) => {
return Math.round(size * widthRatio);
};
export const verticalScale = (size: number) => {
return Math.round(size * heightRatio);
};
export const moderateScale = (size: number, factor = 0.5) => {
return Math.round(size + (scale(size) - size) * factor);
};
// 获取屏幕尺寸
export const getScreenWidth = () => SCREEN_WIDTH;
export const getScreenHeight = () => SCREEN_HEIGHT;
// 获取状态栏高度
export const getStatusBarHeight = () => statusBarHeight;
// 判断是否为小屏幕设备
export const isSmallDevice = () => SCREEN_WIDTH < 375;
// 判断是否为平板设备
export const isTablet = () => {
const pixelDensity = PixelRatio.get();
const adjustedWidth = SCREEN_WIDTH * pixelDensity;
const adjustedHeight = SCREEN_HEIGHT * pixelDensity;
return Math.sqrt(Math.pow(adjustedWidth, 2) + Math.pow(adjustedHeight, 2)) >= 1000;
};

1
app/utils/size.ts

@ -0,0 +1 @@

17
babel.config.js

@ -0,0 +1,17 @@
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: [
[
'@hancleee/babel-plugin-react-native-pxtodp',
{
designWidth: 430, // 设计稿宽度
designHeight: 932, // 设计稿高度
exclude: /node_modules/, // 排除 node_modules 目录
include: /src|app/, // 只处理 src 和 app 目录
},
],
],
};
};

35
package-lock.json generated

@ -24,6 +24,7 @@
"react-i18next": "^15.4.1", "react-i18next": "^15.4.1",
"react-native": "0.76.7", "react-native": "0.76.7",
"react-native-localize": "^3.4.1", "react-native-localize": "^3.4.1",
"react-native-responsive-fontsize": "^0.5.1",
"react-native-safe-area-context": "^5.3.0", "react-native-safe-area-context": "^5.3.0",
"react-native-screens": "^4.10.0", "react-native-screens": "^4.10.0",
"react-native-vector-icons": "^10.2.0", "react-native-vector-icons": "^10.2.0",
@ -31,6 +32,7 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.25.2", "@babel/core": "^7.25.2",
"@hancleee/babel-plugin-react-native-pxtodp": "^1.0.8",
"@types/react": "~18.3.12", "@types/react": "~18.3.12",
"@types/react-native-vector-icons": "^6.4.18", "@types/react-native-vector-icons": "^6.4.18",
"typescript": "^5.3.3" "typescript": "^5.3.3"
@ -2820,6 +2822,18 @@
"js-yaml": "bin/js-yaml.js" "js-yaml": "bin/js-yaml.js"
} }
}, },
"node_modules/@hancleee/babel-plugin-react-native-pxtodp": {
"version": "1.0.8",
"resolved": "https://registry.npmmirror.com/@hancleee/babel-plugin-react-native-pxtodp/-/babel-plugin-react-native-pxtodp-1.0.8.tgz",
"integrity": "sha512-WCy1faXgMVu71tvLWl/jVY+nGhy/D67ersa/slalRzSSCRFibIbE5sDb4PMQOn8Eka3N6tfZYeIMp6ggoW8+sg==",
"dev": true,
"license": "ISC",
"dependencies": {
"@babel/core": "^7.12.9",
"@babel/helper-plugin-utils": "^7.22.5",
"@babel/parser": "^7.21.3"
}
},
"node_modules/@isaacs/cliui": { "node_modules/@isaacs/cliui": {
"version": "8.0.2", "version": "8.0.2",
"resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz", "resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz",
@ -9107,6 +9121,15 @@
} }
} }
}, },
"node_modules/react-native-iphone-x-helper": {
"version": "1.3.1",
"resolved": "https://registry.npmmirror.com/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz",
"integrity": "sha512-HOf0jzRnq2/aFUcdCJ9w9JGzN3gdEg0zFE4FyYlp4jtidqU03D5X7ZegGKfT1EWteR0gPBGp9ye5T5FvSWi9Yg==",
"license": "MIT",
"peerDependencies": {
"react-native": ">=0.42.0"
}
},
"node_modules/react-native-localize": { "node_modules/react-native-localize": {
"version": "3.4.1", "version": "3.4.1",
"resolved": "https://registry.npmmirror.com/react-native-localize/-/react-native-localize-3.4.1.tgz", "resolved": "https://registry.npmmirror.com/react-native-localize/-/react-native-localize-3.4.1.tgz",
@ -9123,6 +9146,18 @@
} }
} }
}, },
"node_modules/react-native-responsive-fontsize": {
"version": "0.5.1",
"resolved": "https://registry.npmmirror.com/react-native-responsive-fontsize/-/react-native-responsive-fontsize-0.5.1.tgz",
"integrity": "sha512-G77iPzrf3BHxMxVm6G3Mw3vPImIdq+jLLXhYoOjWei6i9J3/jzUNUhNdRWvp49Csb5prhbVBLPM+pYZz+b3ESQ==",
"license": "MIT",
"dependencies": {
"react-native-iphone-x-helper": "^1.3.1"
},
"peerDependencies": {
"react-native": ">=0.42.0"
}
},
"node_modules/react-native-safe-area-context": { "node_modules/react-native-safe-area-context": {
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmmirror.com/react-native-safe-area-context/-/react-native-safe-area-context-5.3.0.tgz", "resolved": "https://registry.npmmirror.com/react-native-safe-area-context/-/react-native-safe-area-context-5.3.0.tgz",

2
package.json

@ -25,6 +25,7 @@
"react-i18next": "^15.4.1", "react-i18next": "^15.4.1",
"react-native": "0.76.7", "react-native": "0.76.7",
"react-native-localize": "^3.4.1", "react-native-localize": "^3.4.1",
"react-native-responsive-fontsize": "^0.5.1",
"react-native-safe-area-context": "^5.3.0", "react-native-safe-area-context": "^5.3.0",
"react-native-screens": "^4.10.0", "react-native-screens": "^4.10.0",
"react-native-vector-icons": "^10.2.0", "react-native-vector-icons": "^10.2.0",
@ -32,6 +33,7 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.25.2", "@babel/core": "^7.25.2",
"@hancleee/babel-plugin-react-native-pxtodp": "^1.0.8",
"@types/react": "~18.3.12", "@types/react": "~18.3.12",
"@types/react-native-vector-icons": "^6.4.18", "@types/react-native-vector-icons": "^6.4.18",
"typescript": "^5.3.3" "typescript": "^5.3.3"

27
yarn.lock

@ -43,7 +43,7 @@
resolved "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.26.8.tgz" resolved "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.26.8.tgz"
integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ== integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==
"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.0.0-0 || ^8.0.0-0 <8.0.0", "@babel/core@^7.11.6", "@babel/core@^7.12.0", "@babel/core@^7.12.3", "@babel/core@^7.13.0", "@babel/core@^7.13.16", "@babel/core@^7.20.0", "@babel/core@^7.25.2", "@babel/core@^7.4.0 || ^8.0.0-0 <8.0.0", "@babel/core@^7.8.0": "@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.0.0-0 || ^8.0.0-0 <8.0.0", "@babel/core@^7.11.6", "@babel/core@^7.12.0", "@babel/core@^7.12.3", "@babel/core@^7.12.9", "@babel/core@^7.13.0", "@babel/core@^7.13.16", "@babel/core@^7.20.0", "@babel/core@^7.25.2", "@babel/core@^7.4.0 || ^8.0.0-0 <8.0.0", "@babel/core@^7.8.0":
version "7.26.10" version "7.26.10"
resolved "https://registry.npmmirror.com/@babel/core/-/core-7.26.10.tgz" resolved "https://registry.npmmirror.com/@babel/core/-/core-7.26.10.tgz"
integrity sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ== integrity sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==
@ -231,7 +231,7 @@
js-tokens "^4.0.0" js-tokens "^4.0.0"
picocolors "^1.0.0" picocolors "^1.0.0"
"@babel/parser@^7.1.0", "@babel/parser@^7.13.16", "@babel/parser@^7.14.7", "@babel/parser@^7.20.0", "@babel/parser@^7.20.7", "@babel/parser@^7.25.3", "@babel/parser@^7.26.10", "@babel/parser@^7.27.0": "@babel/parser@^7.1.0", "@babel/parser@^7.13.16", "@babel/parser@^7.14.7", "@babel/parser@^7.20.0", "@babel/parser@^7.20.7", "@babel/parser@^7.21.3", "@babel/parser@^7.25.3", "@babel/parser@^7.26.10", "@babel/parser@^7.27.0":
version "7.27.0" version "7.27.0"
resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.27.0.tgz" resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.27.0.tgz"
integrity sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg== integrity sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==
@ -1462,6 +1462,15 @@
find-up "^5.0.0" find-up "^5.0.0"
js-yaml "^4.1.0" js-yaml "^4.1.0"
"@hancleee/babel-plugin-react-native-pxtodp@^1.0.8":
version "1.0.8"
resolved "https://registry.npmmirror.com/@hancleee/babel-plugin-react-native-pxtodp/-/babel-plugin-react-native-pxtodp-1.0.8.tgz"
integrity sha512-WCy1faXgMVu71tvLWl/jVY+nGhy/D67ersa/slalRzSSCRFibIbE5sDb4PMQOn8Eka3N6tfZYeIMp6ggoW8+sg==
dependencies:
"@babel/core" "^7.12.9"
"@babel/helper-plugin-utils" "^7.22.5"
"@babel/parser" "^7.21.3"
"@isaacs/cliui@^8.0.2": "@isaacs/cliui@^8.0.2":
version "8.0.2" version "8.0.2"
resolved "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz" resolved "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz"
@ -5159,11 +5168,23 @@ react-is@^18.0.0, react-is@^18.2.0:
resolved "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz" resolved "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz"
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
react-native-iphone-x-helper@^1.3.1:
version "1.3.1"
resolved "https://registry.npmmirror.com/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz"
integrity sha512-HOf0jzRnq2/aFUcdCJ9w9JGzN3gdEg0zFE4FyYlp4jtidqU03D5X7ZegGKfT1EWteR0gPBGp9ye5T5FvSWi9Yg==
react-native-localize@^3.4.1: react-native-localize@^3.4.1:
version "3.4.1" version "3.4.1"
resolved "https://registry.npmmirror.com/react-native-localize/-/react-native-localize-3.4.1.tgz" resolved "https://registry.npmmirror.com/react-native-localize/-/react-native-localize-3.4.1.tgz"
integrity sha512-NJqJGBUpHtD/MpLCCkrNiqNZ+xFwbHCivxaoN1Oeb8tBAiZr/IqgP3E+MgnqmmdTMOJ33llUfiW3EM6pEIb33w== integrity sha512-NJqJGBUpHtD/MpLCCkrNiqNZ+xFwbHCivxaoN1Oeb8tBAiZr/IqgP3E+MgnqmmdTMOJ33llUfiW3EM6pEIb33w==
react-native-responsive-fontsize@^0.5.1:
version "0.5.1"
resolved "https://registry.npmmirror.com/react-native-responsive-fontsize/-/react-native-responsive-fontsize-0.5.1.tgz"
integrity sha512-G77iPzrf3BHxMxVm6G3Mw3vPImIdq+jLLXhYoOjWei6i9J3/jzUNUhNdRWvp49Csb5prhbVBLPM+pYZz+b3ESQ==
dependencies:
react-native-iphone-x-helper "^1.3.1"
react-native-safe-area-context@^5.3.0, "react-native-safe-area-context@>= 4.0.0": react-native-safe-area-context@^5.3.0, "react-native-safe-area-context@>= 4.0.0":
version "5.3.0" version "5.3.0"
resolved "https://registry.npmmirror.com/react-native-safe-area-context/-/react-native-safe-area-context-5.3.0.tgz" resolved "https://registry.npmmirror.com/react-native-safe-area-context/-/react-native-safe-area-context-5.3.0.tgz"
@ -5199,7 +5220,7 @@ react-native-web@~0.19.13:
postcss-value-parser "^4.2.0" postcss-value-parser "^4.2.0"
styleq "^0.1.3" styleq "^0.1.3"
react-native@*, "react-native@^0.0.0-0 || >=0.65 <1.0", react-native@>=0.70.0, react-native@0.76.7: react-native@*, "react-native@^0.0.0-0 || >=0.65 <1.0", react-native@>=0.42.0, react-native@>=0.70.0, react-native@0.76.7:
version "0.76.7" version "0.76.7"
resolved "https://registry.npmmirror.com/react-native/-/react-native-0.76.7.tgz" resolved "https://registry.npmmirror.com/react-native/-/react-native-0.76.7.tgz"
integrity sha512-GPJcQeO3qUi1MvuhsC2DC6tH8gJQ4uc4JWPORrdeuCGFWE3QLsN8/hiChTEvJREHLfQSV61YPI8gIOtAQ8c37g== integrity sha512-GPJcQeO3qUi1MvuhsC2DC6tH8gJQ4uc4JWPORrdeuCGFWE3QLsN8/hiChTEvJREHLfQSV61YPI8gIOtAQ8c37g==

Loading…
Cancel
Save