fix: bug
This commit is contained in:
@@ -21,7 +21,6 @@ import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
|
|||||||
import { DownArrowIcon, PointsIcon, SearchIcon } from '@/components/icon';
|
import { DownArrowIcon, PointsIcon, SearchIcon } from '@/components/icon';
|
||||||
import ErrorState from '@/components/ErrorState';
|
import ErrorState from '@/components/ErrorState';
|
||||||
import LoadingState from '@/components/LoadingState';
|
import LoadingState from '@/components/LoadingState';
|
||||||
import RefreshControl from '@/components/RefreshControl';
|
|
||||||
import { useActivates } from '@/hooks/use-activates';
|
import { useActivates } from '@/hooks/use-activates';
|
||||||
import { useCategories } from '@/hooks/use-categories';
|
import { useCategories } from '@/hooks/use-categories';
|
||||||
import { CategoryTemplate } from '@repo/sdk';
|
import { CategoryTemplate } from '@repo/sdk';
|
||||||
@@ -84,7 +83,6 @@ export default function HomeScreen() {
|
|||||||
const params = useLocalSearchParams()
|
const params = useLocalSearchParams()
|
||||||
const [activeTab, setActiveTab] = useState(0)
|
const [activeTab, setActiveTab] = useState(0)
|
||||||
const [selectedCategoryId, setSelectedCategoryId] = useState<string | null>(null)
|
const [selectedCategoryId, setSelectedCategoryId] = useState<string | null>(null)
|
||||||
const [refreshing, setRefreshing] = useState(false)
|
|
||||||
|
|
||||||
const { load: loadCategories, data: categoriesData, loading: categoriesLoading, error: categoriesError } = useCategories()
|
const { load: loadCategories, data: categoriesData, loading: categoriesLoading, error: categoriesError } = useCategories()
|
||||||
const { load, data: activatesData, error } = useActivates()
|
const { load, data: activatesData, error } = useActivates()
|
||||||
@@ -94,12 +92,6 @@ export default function HomeScreen() {
|
|||||||
loadCategories()
|
loadCategories()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleRefresh = async () => {
|
|
||||||
setRefreshing(true)
|
|
||||||
await Promise.all([load(), loadCategories()])
|
|
||||||
setRefreshing(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 当分类数据加载完成后,默认选中第一个分类
|
// 当分类数据加载完成后,默认选中第一个分类
|
||||||
if (categoriesData?.categories && categoriesData.categories.length > 0 && !selectedCategoryId) {
|
if (categoriesData?.categories && categoriesData.categories.length > 0 && !selectedCategoryId) {
|
||||||
@@ -144,14 +136,6 @@ export default function HomeScreen() {
|
|||||||
// 过滤掉视频类型的模板,只显示图片
|
// 过滤掉视频类型的模板,只显示图片
|
||||||
const previewUrl = (template as any).webpPreviewUrl || template.previewUrl || template.coverImageUrl || ``
|
const previewUrl = (template as any).webpPreviewUrl || template.previewUrl || template.coverImageUrl || ``
|
||||||
const isVideo = previewUrl.includes('.mp4') || previewUrl.includes('.mov') || previewUrl.includes('.webm')
|
const isVideo = previewUrl.includes('.mp4') || previewUrl.includes('.mov') || previewUrl.includes('.webm')
|
||||||
console.log(`[HOME PAGE] Filtering ${template.title}:`, {
|
|
||||||
webpPreviewUrl: (template as any).webpPreviewUrl,
|
|
||||||
previewUrl: template.previewUrl,
|
|
||||||
coverImageUrl: template.coverImageUrl,
|
|
||||||
finalPreviewUrl: previewUrl,
|
|
||||||
isVideo,
|
|
||||||
shouldKeep: !isVideo
|
|
||||||
})
|
|
||||||
return !isVideo
|
return !isVideo
|
||||||
})
|
})
|
||||||
: []
|
: []
|
||||||
@@ -297,9 +281,6 @@ export default function HomeScreen() {
|
|||||||
style={styles.scrollView}
|
style={styles.scrollView}
|
||||||
contentContainerStyle={styles.scrollContent}
|
contentContainerStyle={styles.scrollContent}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
refreshControl={
|
|
||||||
<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />
|
|
||||||
}
|
|
||||||
onScroll={(event) => {
|
onScroll={(event) => {
|
||||||
const scrollY = event.nativeEvent.contentOffset.y
|
const scrollY = event.nativeEvent.contentOffset.y
|
||||||
if (scrollY >= tabsPositionRef.current) {
|
if (scrollY >= tabsPositionRef.current) {
|
||||||
@@ -488,6 +469,7 @@ const styles = StyleSheet.create({
|
|||||||
backgroundColor: '#090A0B',
|
backgroundColor: '#090A0B',
|
||||||
},
|
},
|
||||||
scrollContent: {
|
scrollContent: {
|
||||||
|
flexGrow: 1,
|
||||||
backgroundColor: '#090A0B',
|
backgroundColor: '#090A0B',
|
||||||
},
|
},
|
||||||
heroSection: {
|
heroSection: {
|
||||||
@@ -615,10 +597,8 @@ const styles = StyleSheet.create({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
gridContainer: {
|
gridContainer: {
|
||||||
flexDirection: 'row',
|
flex: 1,
|
||||||
flexWrap: 'wrap',
|
|
||||||
paddingHorizontal: 8,
|
paddingHorizontal: 8,
|
||||||
justifyContent: 'space-between',
|
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
ScrollView,
|
ScrollView,
|
||||||
StatusBar as RNStatusBar,
|
StatusBar as RNStatusBar,
|
||||||
Pressable,
|
Pressable,
|
||||||
RefreshControl,
|
|
||||||
NativeScrollEvent,
|
NativeScrollEvent,
|
||||||
NativeSyntheticEvent,
|
NativeSyntheticEvent,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
@@ -276,13 +275,6 @@ export default function MessageScreen() {
|
|||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
scrollEventThrottle={400}
|
scrollEventThrottle={400}
|
||||||
refreshControl={
|
|
||||||
<RefreshControl
|
|
||||||
refreshing={loading && messages.length > 0}
|
|
||||||
onRefresh={refetch}
|
|
||||||
tintColor="#FFFFFF"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{messages.length > 0 ? (
|
{messages.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
Dimensions,
|
Dimensions,
|
||||||
Pressable,
|
Pressable,
|
||||||
StatusBar as RNStatusBar,
|
StatusBar as RNStatusBar,
|
||||||
RefreshControl,
|
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import { StatusBar } from 'expo-status-bar'
|
import { StatusBar } from 'expo-status-bar'
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||||
@@ -38,7 +37,6 @@ export default function My() {
|
|||||||
const { t, i18n } = useTranslation()
|
const { t, i18n } = useTranslation()
|
||||||
const [editDrawerVisible, setEditDrawerVisible] = useState(false)
|
const [editDrawerVisible, setEditDrawerVisible] = useState(false)
|
||||||
const [profileName, setProfileName] = useState('乔乔乔乔')
|
const [profileName, setProfileName] = useState('乔乔乔乔')
|
||||||
const [refreshing, setRefreshing] = useState(false)
|
|
||||||
|
|
||||||
// 使用 useTemplateGenerations hook 获取用户作品列表
|
// 使用 useTemplateGenerations hook 获取用户作品列表
|
||||||
const {
|
const {
|
||||||
@@ -54,13 +52,6 @@ export default function My() {
|
|||||||
loadGenerations({ page: 1, limit: 50 })
|
loadGenerations({ page: 1, limit: 50 })
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// 处理下拉刷新
|
|
||||||
const handleRefresh = useCallback(async () => {
|
|
||||||
setRefreshing(true)
|
|
||||||
await refetch({ page: 1, limit: 50 })
|
|
||||||
setRefreshing(false)
|
|
||||||
}, [refetch])
|
|
||||||
|
|
||||||
// 处理设置菜单选择
|
// 处理设置菜单选择
|
||||||
const handleSettingsSelect = async (value: string) => {
|
const handleSettingsSelect = async (value: string) => {
|
||||||
if (value === 'changePassword') {
|
if (value === 'changePassword') {
|
||||||
@@ -179,21 +170,13 @@ export default function My() {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 作品九宫格 */}
|
{/* 作品九宫格 */}
|
||||||
{loading && !refreshing ? (
|
{loading ? (
|
||||||
<MySkeleton />
|
<MySkeleton />
|
||||||
) : (
|
) : (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={styles.scrollView}
|
style={styles.scrollView}
|
||||||
contentContainerStyle={styles.scrollContent}
|
contentContainerStyle={styles.scrollContent}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
refreshControl={
|
|
||||||
<RefreshControl
|
|
||||||
refreshing={refreshing}
|
|
||||||
onRefresh={handleRefresh}
|
|
||||||
tintColor="#FFFFFF"
|
|
||||||
colors={['#FFFFFF']}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<View style={styles.galleryGrid}>
|
<View style={styles.galleryGrid}>
|
||||||
{generations.map((item, index) => (
|
{generations.map((item, index) => (
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import { useRouter } from 'expo-router'
|
|||||||
import { useTemplates, type TemplateDetail } from '@/hooks'
|
import { useTemplates, type TemplateDetail } from '@/hooks'
|
||||||
import LoadingState from '@/components/LoadingState'
|
import LoadingState from '@/components/LoadingState'
|
||||||
import ErrorState from '@/components/ErrorState'
|
import ErrorState from '@/components/ErrorState'
|
||||||
import RefreshControl from '@/components/RefreshControl'
|
|
||||||
import PaginationLoader from '@/components/PaginationLoader'
|
import PaginationLoader from '@/components/PaginationLoader'
|
||||||
|
|
||||||
const { width: screenWidth, height: screenHeight } = Dimensions.get('window')
|
const { width: screenWidth, height: screenHeight } = Dimensions.get('window')
|
||||||
@@ -124,16 +123,13 @@ export default function VideoScreen() {
|
|||||||
page: 1,
|
page: 1,
|
||||||
limit: PAGE_SIZE,
|
limit: PAGE_SIZE,
|
||||||
})
|
})
|
||||||
const [refreshing, setRefreshing] = useState(false)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
execute()
|
execute()
|
||||||
}, [execute])
|
}, [execute])
|
||||||
|
|
||||||
const handleRefresh = useCallback(async () => {
|
const handleRefresh = useCallback(async () => {
|
||||||
setRefreshing(true)
|
|
||||||
await refetch()
|
await refetch()
|
||||||
setRefreshing(false)
|
|
||||||
}, [refetch])
|
}, [refetch])
|
||||||
|
|
||||||
const handleLoadMore = useCallback(() => {
|
const handleLoadMore = useCallback(() => {
|
||||||
@@ -220,12 +216,6 @@ export default function VideoScreen() {
|
|||||||
snapToInterval={videoHeight}
|
snapToInterval={videoHeight}
|
||||||
snapToAlignment="start"
|
snapToAlignment="start"
|
||||||
decelerationRate="fast"
|
decelerationRate="fast"
|
||||||
refreshControl={
|
|
||||||
<RefreshControl
|
|
||||||
refreshing={refreshing}
|
|
||||||
onRefresh={handleRefresh}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
onEndReached={handleLoadMore}
|
onEndReached={handleLoadMore}
|
||||||
onEndReachedThreshold={0.1}
|
onEndReachedThreshold={0.1}
|
||||||
ListFooterComponent={loading ? <PaginationLoader color="#FFE500" /> : null}
|
ListFooterComponent={loading ? <PaginationLoader color="#FFE500" /> : null}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import { DeleteConfirmDialog } from '@/components/ui/delete-confirm-dialog'
|
|||||||
import LoadingState from '@/components/LoadingState'
|
import LoadingState from '@/components/LoadingState'
|
||||||
import ErrorState from '@/components/ErrorState'
|
import ErrorState from '@/components/ErrorState'
|
||||||
import PaginationLoader from '@/components/PaginationLoader'
|
import PaginationLoader from '@/components/PaginationLoader'
|
||||||
import RefreshControl from '@/components/RefreshControl'
|
|
||||||
import { useTemplateGenerations, type TemplateGeneration } from '@/hooks'
|
import { useTemplateGenerations, type TemplateGeneration } from '@/hooks'
|
||||||
|
|
||||||
const { width: screenWidth } = Dimensions.get('window')
|
const { width: screenWidth } = Dimensions.get('window')
|
||||||
@@ -154,7 +153,6 @@ export default function GenerationRecordScreen() {
|
|||||||
keyExtractor={(item) => item.id}
|
keyExtractor={(item) => item.id}
|
||||||
contentContainerStyle={styles.scrollContent}
|
contentContainerStyle={styles.scrollContent}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
refreshControl={<RefreshControl refreshing={loading} onRefresh={handleRefresh} />}
|
|
||||||
onEndReached={handleLoadMore}
|
onEndReached={handleLoadMore}
|
||||||
onEndReachedThreshold={0.5}
|
onEndReachedThreshold={0.5}
|
||||||
ListFooterComponent={loadingMore ? <PaginationLoader testID="pagination-loader" /> : null}
|
ListFooterComponent={loadingMore ? <PaginationLoader testID="pagination-loader" /> : null}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { useRouter, useLocalSearchParams } from 'expo-router'
|
|||||||
|
|
||||||
import SearchResultsGrid from '@/components/SearchResultsGrid'
|
import SearchResultsGrid from '@/components/SearchResultsGrid'
|
||||||
import SearchBar from '@/components/SearchBar'
|
import SearchBar from '@/components/SearchBar'
|
||||||
import RefreshControl from '@/components/RefreshControl'
|
|
||||||
import LoadingState from '@/components/LoadingState'
|
import LoadingState from '@/components/LoadingState'
|
||||||
import ErrorState from '@/components/ErrorState'
|
import ErrorState from '@/components/ErrorState'
|
||||||
import PaginationLoader from '@/components/PaginationLoader'
|
import PaginationLoader from '@/components/PaginationLoader'
|
||||||
@@ -21,7 +20,6 @@ export default function SearchResultsScreen() {
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const params = useLocalSearchParams()
|
const params = useLocalSearchParams()
|
||||||
const [searchText, setSearchText] = useState((params.q as string) || '')
|
const [searchText, setSearchText] = useState((params.q as string) || '')
|
||||||
const [refreshing, setRefreshing] = useState(false)
|
|
||||||
|
|
||||||
const { templates, loading, loadingMore, error, execute, refetch, loadMore, hasMore } = useTemplates()
|
const { templates, loading, loadingMore, error, execute, refetch, loadMore, hasMore } = useTemplates()
|
||||||
const { addToHistory } = useSearchHistory()
|
const { addToHistory } = useSearchHistory()
|
||||||
@@ -38,11 +36,9 @@ export default function SearchResultsScreen() {
|
|||||||
}, [params.q, execute, addToHistory])
|
}, [params.q, execute, addToHistory])
|
||||||
|
|
||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
setRefreshing(true)
|
|
||||||
if (searchText) {
|
if (searchText) {
|
||||||
await refetch()
|
await refetch()
|
||||||
}
|
}
|
||||||
setRefreshing(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleLoadMore = () => {
|
const handleLoadMore = () => {
|
||||||
@@ -164,7 +160,6 @@ export default function SearchResultsScreen() {
|
|||||||
<SearchResultsGrid
|
<SearchResultsGrid
|
||||||
results={searchResults}
|
results={searchResults}
|
||||||
loading={false}
|
loading={false}
|
||||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />}
|
|
||||||
onEndReached={handleLoadMore}
|
onEndReached={handleLoadMore}
|
||||||
ListFooterComponent={loadingMore ? <PaginationLoader /> : null}
|
ListFooterComponent={loadingMore ? <PaginationLoader /> : null}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { useTranslation } from 'react-i18next'
|
|||||||
|
|
||||||
import { LeftArrowIcon, SearchIcon } from '@/components/icon'
|
import { LeftArrowIcon, SearchIcon } from '@/components/icon'
|
||||||
import WorksGallery, { type Category, type WorkItem } from '@/components/WorksGallery'
|
import WorksGallery, { type Category, type WorkItem } from '@/components/WorksGallery'
|
||||||
import RefreshControl from '@/components/RefreshControl'
|
|
||||||
import LoadingState from '@/components/LoadingState'
|
import LoadingState from '@/components/LoadingState'
|
||||||
import ErrorState from '@/components/ErrorState'
|
import ErrorState from '@/components/ErrorState'
|
||||||
import PaginationLoader from '@/components/PaginationLoader'
|
import PaginationLoader from '@/components/PaginationLoader'
|
||||||
@@ -95,9 +94,6 @@ export default function WorksListScreen() {
|
|||||||
params: { id: id.toString() },
|
params: { id: id.toString() },
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
refreshControl={
|
|
||||||
<RefreshControl refreshing={refreshing} onRefresh={refresh} />
|
|
||||||
}
|
|
||||||
onEndReached={loadMore}
|
onEndReached={loadMore}
|
||||||
ListFooterComponent={
|
ListFooterComponent={
|
||||||
hasMore ? <PaginationLoader /> : undefined
|
hasMore ? <PaginationLoader /> : undefined
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { render } from '@testing-library/react-native'
|
|
||||||
import { ScrollView } from 'react-native'
|
|
||||||
import RefreshControl from './RefreshControl'
|
|
||||||
|
|
||||||
describe('RefreshControl Component', () => {
|
|
||||||
describe('Basic Rendering', () => {
|
|
||||||
it('should render with refreshing false', () => {
|
|
||||||
const mockOnRefresh = jest.fn()
|
|
||||||
const { UNSAFE_root } = render(
|
|
||||||
<ScrollView
|
|
||||||
refreshControl={
|
|
||||||
<RefreshControl refreshing={false} onRefresh={mockOnRefresh} />
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<></>
|
|
||||||
</ScrollView>
|
|
||||||
)
|
|
||||||
expect(UNSAFE_root).toBeTruthy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should render with refreshing true', () => {
|
|
||||||
const mockOnRefresh = jest.fn()
|
|
||||||
const { UNSAFE_root } = render(
|
|
||||||
<ScrollView
|
|
||||||
refreshControl={
|
|
||||||
<RefreshControl refreshing={true} onRefresh={mockOnRefresh} />
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<></>
|
|
||||||
</ScrollView>
|
|
||||||
)
|
|
||||||
expect(UNSAFE_root).toBeTruthy()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Props', () => {
|
|
||||||
it('should accept refreshing prop', () => {
|
|
||||||
const mockOnRefresh = jest.fn()
|
|
||||||
const { UNSAFE_root } = render(
|
|
||||||
<ScrollView
|
|
||||||
refreshControl={
|
|
||||||
<RefreshControl refreshing={true} onRefresh={mockOnRefresh} />
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<></>
|
|
||||||
</ScrollView>
|
|
||||||
)
|
|
||||||
expect(UNSAFE_root).toBeTruthy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should accept onRefresh callback prop', () => {
|
|
||||||
const mockOnRefresh = jest.fn()
|
|
||||||
const { UNSAFE_root } = render(
|
|
||||||
<ScrollView
|
|
||||||
refreshControl={
|
|
||||||
<RefreshControl refreshing={false} onRefresh={mockOnRefresh} />
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<></>
|
|
||||||
</ScrollView>
|
|
||||||
)
|
|
||||||
expect(UNSAFE_root).toBeTruthy()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { RefreshControl as RNRefreshControl } from 'react-native'
|
|
||||||
|
|
||||||
interface RefreshControlProps {
|
|
||||||
refreshing: boolean
|
|
||||||
onRefresh: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function RefreshControl({
|
|
||||||
refreshing,
|
|
||||||
onRefresh,
|
|
||||||
}: RefreshControlProps) {
|
|
||||||
return (
|
|
||||||
<RNRefreshControl
|
|
||||||
refreshing={refreshing}
|
|
||||||
onRefresh={onRefresh}
|
|
||||||
tintColor="#F5F5F5"
|
|
||||||
colors={['#F5F5F5']}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -30,7 +30,6 @@ interface TemplateSearchResultItem {
|
|||||||
interface SearchResultsGridProps {
|
interface SearchResultsGridProps {
|
||||||
results: TemplateSearchResultItem[]
|
results: TemplateSearchResultItem[]
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
refreshControl?: React.ReactElement
|
|
||||||
onEndReached?: () => void
|
onEndReached?: () => void
|
||||||
ListFooterComponent?: React.ReactElement | null
|
ListFooterComponent?: React.ReactElement | null
|
||||||
}
|
}
|
||||||
@@ -44,7 +43,7 @@ const calculateCardHeight = (width: number, aspectRatio?: number): number => {
|
|||||||
return width * 1.2
|
return width * 1.2
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SearchResultsGrid({ results, loading, refreshControl, onEndReached, ListFooterComponent }: SearchResultsGridProps) {
|
export default function SearchResultsGrid({ results, loading, onEndReached, ListFooterComponent }: SearchResultsGridProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [gridWidth, setGridWidth] = useState(screenWidth)
|
const [gridWidth, setGridWidth] = useState(screenWidth)
|
||||||
@@ -88,7 +87,6 @@ export default function SearchResultsGrid({ results, loading, refreshControl, on
|
|||||||
<ScrollView
|
<ScrollView
|
||||||
style={styles.scrollView}
|
style={styles.scrollView}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
refreshControl={refreshControl}
|
|
||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
scrollEventThrottle={400}
|
scrollEventThrottle={400}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ interface WorksGalleryProps {
|
|||||||
onCategoryChange: (category: Category) => void
|
onCategoryChange: (category: Category) => void
|
||||||
groupedWorks: Record<string, WorkItem[]>
|
groupedWorks: Record<string, WorkItem[]>
|
||||||
onWorkPress: (id: number) => void
|
onWorkPress: (id: number) => void
|
||||||
refreshControl?: React.ReactElement
|
|
||||||
onEndReached?: () => void
|
onEndReached?: () => void
|
||||||
ListFooterComponent?: React.ReactElement
|
ListFooterComponent?: React.ReactElement
|
||||||
}
|
}
|
||||||
@@ -43,7 +42,6 @@ export default function WorksGallery({
|
|||||||
onCategoryChange,
|
onCategoryChange,
|
||||||
groupedWorks,
|
groupedWorks,
|
||||||
onWorkPress,
|
onWorkPress,
|
||||||
refreshControl,
|
|
||||||
onEndReached,
|
onEndReached,
|
||||||
ListFooterComponent,
|
ListFooterComponent,
|
||||||
}: WorksGalleryProps) {
|
}: WorksGalleryProps) {
|
||||||
@@ -118,7 +116,6 @@ export default function WorksGallery({
|
|||||||
style={styles.scrollView}
|
style={styles.scrollView}
|
||||||
contentContainerStyle={styles.scrollContent}
|
contentContainerStyle={styles.scrollContent}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
refreshControl={refreshControl}
|
|
||||||
onScroll={(e) => {
|
onScroll={(e) => {
|
||||||
const { layoutMeasurement, contentOffset, contentSize } = e.nativeEvent
|
const { layoutMeasurement, contentOffset, contentSize } = e.nativeEvent
|
||||||
const isCloseToBottom = layoutMeasurement.height + contentOffset.y >= contentSize.height - 100
|
const isCloseToBottom = layoutMeasurement.height + contentOffset.y >= contentSize.height - 100
|
||||||
|
|||||||
@@ -1,3 +1,42 @@
|
|||||||
|
// Mock react-native BEFORE any other imports to avoid flow type issues
|
||||||
|
jest.mock('react-native', () => {
|
||||||
|
const React = require('react')
|
||||||
|
return {
|
||||||
|
...jest.requireActual('react-native'),
|
||||||
|
RefreshControl: 'RefreshControl',
|
||||||
|
ScrollView: 'ScrollView',
|
||||||
|
FlatList: 'FlatList',
|
||||||
|
View: 'View',
|
||||||
|
Text: 'Text',
|
||||||
|
Image: 'Image',
|
||||||
|
Pressable: 'Pressable',
|
||||||
|
Platform: { OS: 'web' },
|
||||||
|
Animated: {
|
||||||
|
View: 'Animated.View',
|
||||||
|
Text: 'Animated.Text',
|
||||||
|
Image: 'Animated.Image',
|
||||||
|
Value: class MockValue {
|
||||||
|
constructor(v) { this._value = v }
|
||||||
|
setValue(v) { this._value = v }
|
||||||
|
__getValue() { return this._value }
|
||||||
|
interpolate() { return this }
|
||||||
|
},
|
||||||
|
timing: () => ({ start: jest.fn() }),
|
||||||
|
spring: () => ({ start: jest.fn() }),
|
||||||
|
event: () => jest.fn(),
|
||||||
|
},
|
||||||
|
PanResponder: {
|
||||||
|
create: () => ({ panHandlers: {} }),
|
||||||
|
},
|
||||||
|
ActivityIndicator: 'ActivityIndicator',
|
||||||
|
StyleSheet: { create: (styles) => styles },
|
||||||
|
Dimensions: { get: () => ({ width: 375, height: 812 }) },
|
||||||
|
StatusBar: 'StatusBar',
|
||||||
|
SafeAreaView: 'SafeAreaView',
|
||||||
|
useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
require('@testing-library/jest-native/extend-expect')
|
require('@testing-library/jest-native/extend-expect')
|
||||||
|
|
||||||
// Initialize globals for react-native-reanimated
|
// Initialize globals for react-native-reanimated
|
||||||
@@ -138,6 +177,16 @@ jest.mock('@repo/sdk', () => ({
|
|||||||
CategoryController: class MockCategoryController {},
|
CategoryController: class MockCategoryController {},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// Mock react-native to avoid flow type issues
|
||||||
|
jest.mock('react-native', () => {
|
||||||
|
const RN = jest.requireActual('react-native')
|
||||||
|
return {
|
||||||
|
...RN,
|
||||||
|
// Ensure RefreshControl is properly mocked for tests
|
||||||
|
RefreshControl: 'RefreshControl',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Mock lib/auth to avoid TypeScript compilation errors
|
// Mock lib/auth to avoid TypeScript compilation errors
|
||||||
jest.mock('@/lib/auth', () => ({
|
jest.mock('@/lib/auth', () => ({
|
||||||
OWNER_ID: 'test-owner-id',
|
OWNER_ID: 'test-owner-id',
|
||||||
|
|||||||
@@ -345,4 +345,24 @@ type UnreadAnnouncementCountResult = {
|
|||||||
|
|
||||||
| 错误 | 尝试 | 解决方案 |
|
| 错误 | 尝试 | 解决方案 |
|
||||||
|------|------|----------|
|
|------|------|----------|
|
||||||
| 无 | - | 所有阶段都顺利完成,没有遇到阻塞性错误 |
|
| API验证错误:type参数不接受数组 | 发送type: ["SYSTEM", "ACTIVITY"] | 修改为单个type值,使用客户端过滤实现Tab功能 |
|
||||||
|
|
||||||
|
### 错误详情
|
||||||
|
|
||||||
|
**错误信息**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "VALIDATION_ERROR",
|
||||||
|
"message": "[body.type] Invalid option: expected one of \"SYSTEM\"|\"ACTIVITY\"|\"BILLING\"|\"MARKETING\""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**原因**: SDK的MessageController.list() API只接受单个MessageType值,不支持数组形式的type参数。
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
1. 修改hooks/use-messages.ts中的type参数类型从数组改为单个值
|
||||||
|
2. 在app/(tabs)/message.tsx中使用客户端过滤(filterMessagesByTab函数)
|
||||||
|
3. 获取所有消息后,根据activeTab在客户端过滤显示
|
||||||
|
4. 移除useEffect对activeTab的依赖,避免Tab切换时重新请求API
|
||||||
|
|
||||||
|
**提交**: fix: change type parameter from array to single value for API compatibility
|
||||||
|
|||||||
@@ -19,8 +19,6 @@
|
|||||||
"test:coverage": "./node_modules/.bin/jest --coverage"
|
"test:coverage": "./node_modules/.bin/jest --coverage"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@repo/core": "1.0.3",
|
|
||||||
"@repo/sdk": "1.0.9",
|
|
||||||
"@better-auth/expo": "1.3.34",
|
"@better-auth/expo": "1.3.34",
|
||||||
"@expo/vector-icons": "^15.0.3",
|
"@expo/vector-icons": "^15.0.3",
|
||||||
"@gorhom/bottom-sheet": "^5.2.8",
|
"@gorhom/bottom-sheet": "^5.2.8",
|
||||||
@@ -29,6 +27,8 @@
|
|||||||
"@react-navigation/bottom-tabs": "^7.4.0",
|
"@react-navigation/bottom-tabs": "^7.4.0",
|
||||||
"@react-navigation/elements": "^2.6.3",
|
"@react-navigation/elements": "^2.6.3",
|
||||||
"@react-navigation/native": "^7.1.8",
|
"@react-navigation/native": "^7.1.8",
|
||||||
|
"@repo/core": "1.0.3",
|
||||||
|
"@repo/sdk": "1.0.9",
|
||||||
"@shopify/flash-list": "^2.0.0",
|
"@shopify/flash-list": "^2.0.0",
|
||||||
"@stripe/react-stripe-js": "^5.4.1",
|
"@stripe/react-stripe-js": "^5.4.1",
|
||||||
"@stripe/stripe-js": "^8.5.3",
|
"@stripe/stripe-js": "^8.5.3",
|
||||||
|
|||||||
Reference in New Issue
Block a user