This commit is contained in:
imeepos
2026-01-29 16:20:01 +08:00
parent 7c3b3c066c
commit 1ccdc23355
6 changed files with 315 additions and 229 deletions

View File

@@ -155,12 +155,10 @@ export default function MessageScreen() {
// Render message item // Render message item
const renderMessageItem = useCallback(({ item }: { item: Message }) => ( const renderMessageItem = useCallback(({ item }: { item: Message }) => (
<SwipeToDelete onDelete={() => handleDeleteMessage(item.id)}> <MessageCard
<MessageCard message={item}
message={item} onPress={handleMessagePress}
onPress={handleMessagePress} />
/>
</SwipeToDelete>
), [handleDeleteMessage, handleMessagePress]) ), [handleDeleteMessage, handleMessagePress])
// Render list footer // Render list footer
@@ -288,6 +286,7 @@ const styles = StyleSheet.create({
listContent: { listContent: {
paddingHorizontal: 4, paddingHorizontal: 4,
paddingTop: 12, paddingTop: 12,
paddingBottom: 100,
}, },
emptyListContent: { emptyListContent: {
flex: 1, flex: 1,

View File

@@ -32,7 +32,6 @@ import { MySkeleton } from '@/components/skeleton/MySkeleton'
import { TabNavigation } from '@/components/blocks/home/TabNavigation' import { TabNavigation } from '@/components/blocks/home/TabNavigation'
import { useUserBalance } from '@/hooks/use-user-balance' import { useUserBalance } from '@/hooks/use-user-balance'
import { KeyboardAwareScrollView } from 'react-native-keyboard-controller'
const { width: screenWidth } = Dimensions.get('window') const { width: screenWidth } = Dimensions.get('window')
const GALLERY_GAP = 2 const GALLERY_GAP = 2
@@ -136,9 +135,13 @@ export default function My() {
// 下拉刷新状态 // 下拉刷新状态
const [refreshing, setRefreshing] = useState(false) const [refreshing, setRefreshing] = useState(false)
const [favoritesRefreshing, setFavoritesRefreshing] = useState(false)
const [likesRefreshing, setLikesRefreshing] = useState(false)
// 防止重复触发加载更多 // 防止重复触发加载更多
const isLoadingMoreRef = useRef(false) const isLoadingMoreRef = useRef(false)
const isFavoritesLoadingMoreRef = useRef(false)
const isLikesLoadingMoreRef = useRef(false)
// 下拉刷新处理 // 下拉刷新处理
const onRefresh = useCallback(async () => { const onRefresh = useCallback(async () => {
@@ -150,6 +153,26 @@ export default function My() {
} }
}, [refetch]) }, [refetch])
// 收藏Tab下拉刷新处理
const onFavoritesRefresh = useCallback(async () => {
setFavoritesRefreshing(true)
try {
await favoritesRefetch()
} finally {
setFavoritesRefreshing(false)
}
}, [favoritesRefetch])
// 点赞Tab下拉刷新处理
const onLikesRefresh = useCallback(async () => {
setLikesRefreshing(true)
try {
await likesRefetch()
} finally {
setLikesRefreshing(false)
}
}, [likesRefetch])
// 加载更多处理 // 加载更多处理
const handleScroll = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => { const handleScroll = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => {
const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent
@@ -173,6 +196,40 @@ export default function My() {
} }
}, [loadingMore, hasMore, loadMore, loading]) }, [loadingMore, hasMore, loadMore, loading])
// 收藏Tab加载更多处理
const handleFavoritesScroll = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => {
const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent
const paddingToBottom = 200
const isCloseToBottom =
layoutMeasurement.height + contentOffset.y >= contentSize.height - paddingToBottom
if (isCloseToBottom && !favoritesLoadingMore && favoritesHasMore && !favoritesLoading && !isFavoritesLoadingMoreRef.current) {
console.log('🔄 收藏Tab触发加载更多')
isFavoritesLoadingMoreRef.current = true
favoritesLoadMore().finally(() => {
isFavoritesLoadingMoreRef.current = false
})
}
}, [favoritesLoadingMore, favoritesHasMore, favoritesLoadMore, favoritesLoading])
// 点赞Tab加载更多处理
const handleLikesScroll = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => {
const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent
const paddingToBottom = 200
const isCloseToBottom =
layoutMeasurement.height + contentOffset.y >= contentSize.height - paddingToBottom
if (isCloseToBottom && !likesLoadingMore && likesHasMore && !likesLoading && !isLikesLoadingMoreRef.current) {
console.log('🔄 点赞Tab触发加载更多')
isLikesLoadingMoreRef.current = true
likesLoadMore().finally(() => {
isLikesLoadingMoreRef.current = false
})
}
}, [likesLoadingMore, likesHasMore, likesLoadMore, likesLoading])
// 处理设置菜单选择 // 处理设置菜单选择
const handleSettingsSelect = async (value: string) => { const handleSettingsSelect = async (value: string) => {
if (value === 'changePassword') { if (value === 'changePassword') {
@@ -231,81 +288,169 @@ export default function My() {
] ]
return ( return (
<SafeAreaView style={styles.container} edges={['top', 'bottom']}> <SafeAreaView style={styles.container} edges={['top']}>
<KeyboardAwareScrollView bottomOffset={50}> <StatusBar style="light" />
<RNStatusBar barStyle="light-content" />
<StatusBar style="light" /> {/* 顶部积分与设置 */}
<RNStatusBar barStyle="light-content" /> <View style={styles.topBar}>
<Pressable
{/* 顶部积分与设置 */} style={styles.pointsPill}
<View style={styles.topBar}> onPress={() => router.push('/membership' as any)}
<Pressable >
style={styles.pointsPill} <PointsIcon />
onPress={() => router.push('/membership' as any)} <Text style={styles.pointsPillText}>{balance}</Text>
> </Pressable>
<PointsIcon /> <Dropdown
<Text style={styles.pointsPillText}>{balance}</Text> options={settingsOptions}
</Pressable> onSelect={(value) => handleSettingsSelect(value)}
<Dropdown renderTrigger={(selectedOption, isOpen, toggle) => (
options={settingsOptions} <Pressable onPress={toggle}>
onSelect={(value) => handleSettingsSelect(value)} <SettingsIcon />
renderTrigger={(selectedOption, isOpen, toggle) => ( </Pressable>
<Pressable onPress={toggle}> )}
<SettingsIcon /> dropdownStyle={{
</Pressable> minWidth: 160,
)} right: 10,
dropdownStyle={{ backgroundColor: '#2A2A2A80',
minWidth: 160, }}
right: 10,
backgroundColor: '#2A2A2A80',
}}
/>
</View>
{/* 个人信息区 */}
<View style={styles.profileSection}>
<Image
source={session?.user?.image ? { uri: session.user.image } : require('@/assets/images/icon.png')}
style={styles.avatar}
contentFit="cover"
/>
<View style={styles.profileInfo}>
<Text style={styles.profileName}>{profileName}</Text>
<Text style={styles.profileSubTitle}>ID {session?.user?.id?.slice(0, 8) || '--------'}</Text>
</View>
<Pressable
style={styles.editButton}
onPress={() => {
console.log('[my.tsx] Edit Profile button pressed')
setEditDrawerVisible(true)
}}
>
<Text style={styles.editButtonText}>{t('my.editProfile')}</Text>
</Pressable>
</View>
{/* Tab 导航 */}
<TabNavigation
tabs={tabs}
activeIndex={activeTabIndex}
onTabPress={handleTabPress}
/> />
</View>
{/* 作品九宫格 */} {/* 个人信息区 */}
{activeTab === 'works' && loading ? ( <View style={styles.profileSection}>
<Image
source={session?.user?.image ? { uri: session.user.image } : require('@/assets/images/icon.png')}
style={styles.avatar}
contentFit="cover"
/>
<View style={styles.profileInfo}>
<Text style={styles.profileName}>{profileName}</Text>
<Text style={styles.profileSubTitle}>ID {session?.user?.id?.slice(0, 8) || '--------'}</Text>
</View>
<Pressable
style={styles.editButton}
onPress={() => {
console.log('[my.tsx] Edit Profile button pressed')
setEditDrawerVisible(true)
}}
>
<Text style={styles.editButtonText}>{t('my.editProfile')}</Text>
</Pressable>
</View>
{/* Tab 导航 */}
<TabNavigation
tabs={tabs}
activeIndex={activeTabIndex}
onTabPress={handleTabPress}
/>
{/* 作品九宫格 */}
{activeTab === 'works' && loading ? (
<MySkeleton />
) : activeTab === 'works' ? (
<ScrollView
testID="my-scroll-view"
style={styles.scrollView}
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false}
onScroll={handleScroll}
scrollEventThrottle={16}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
tintColor="#9966FF"
colors={['#9966FF', '#FF6699', '#FF9966']}
progressBackgroundColor="#1C1E22"
progressViewOffset={10}
/>
}
>
<View style={styles.galleryGrid}>
{generations.map((item, index) => (
<Pressable
key={item.id}
style={[
styles.galleryItem,
index % 3 !== 2 && styles.galleryItemMarginRight,
styles.galleryItemMarginBottom,
]}
onPress={() => {
if (item.status === 'completed') {
router.push({
pathname: '/generationRecord' as any,
params: { id: item.id },
})
}
}}
disabled={item.status !== 'completed'}
>
<Image
source={getCoverUrl(item) ? { uri: getCoverUrl(item) } : require('@/assets/images/membership.png')}
style={styles.galleryImage}
contentFit="cover"
/>
{/* 遮罩:非完成状态 */}
{item.status !== 'completed' && (
<View style={styles.generatingOverlay} />
)}
{/* 数量角标:已完成且有结果 */}
{item.status === 'completed' && (item.resultUrl?.length || 0) > 0 && (
<View style={styles.counterBadge}>
<Text style={styles.counterText}>
{item.resultUrl?.length || 1}
</Text>
</View>
)}
{/* 状态角标 */}
{item.status === 'running' && (
<View style={styles.generatingBadge}>
<Text style={styles.generatingBadgeText}>{t('my.generating')}</Text>
</View>
)}
{item.status === 'pending' && (
<View style={styles.generatingBadge}>
<Text style={styles.generatingBadgeText}>{t('my.queuing')}</Text>
</View>
)}
</Pressable>
))}
{/* 加载更多指示器 */}
{loadingMore && (
<View style={styles.loadingMoreContainer}>
<ActivityIndicator size="small" color="#9966FF" />
</View>
)}
{/* 空状态提示 */}
{!loading && generations.length === 0 && (
<View style={styles.emptyState}>
<Text style={styles.emptyStateText}>
{t('my.noWorks')}
</Text>
</View>
)}
</View>
</ScrollView>
) : activeTab === 'favorites' && favoritesLoading ? (
<MySkeleton /> <MySkeleton />
) : activeTab === 'works' ? ( ) : activeTab === 'favorites' ? (
<ScrollView <ScrollView
testID="my-scroll-view"
style={styles.scrollView} style={styles.scrollView}
contentContainerStyle={styles.scrollContent} contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
onScroll={handleScroll} onScroll={handleFavoritesScroll}
scrollEventThrottle={16} scrollEventThrottle={16}
refreshControl={ refreshControl={
<RefreshControl <RefreshControl
refreshing={refreshing} refreshing={favoritesRefreshing}
onRefresh={onRefresh} onRefresh={onFavoritesRefresh}
tintColor="#9966FF" tintColor="#9966FF"
colors={['#9966FF', '#FF6699', '#FF9966']} colors={['#9966FF', '#FF6699', '#FF9966']}
progressBackgroundColor="#1C1E22" progressBackgroundColor="#1C1E22"
@@ -314,107 +459,37 @@ export default function My() {
} }
> >
<View style={styles.galleryGrid}> <View style={styles.galleryGrid}>
{generations.map((item, index) => ( {favorites.length === 0 && !favoritesLoading ? (
<Pressable
key={item.id}
style={[
styles.galleryItem,
index % 3 !== 2 && styles.galleryItemMarginRight,
styles.galleryItemMarginBottom,
]}
onPress={() => {
if (item.status === 'completed') {
router.push({
pathname: '/generationRecord' as any,
params: { id: item.id },
})
}
}}
disabled={item.status !== 'completed'}
>
<Image
source={getCoverUrl(item) ? { uri: getCoverUrl(item) } : require('@/assets/images/membership.png')}
style={styles.galleryImage}
contentFit="cover"
/>
{/* 遮罩:非完成状态 */}
{item.status !== 'completed' && (
<View style={styles.generatingOverlay} />
)}
{/* 数量角标:已完成且有结果 */}
{item.status === 'completed' && (item.resultUrl?.length || 0) > 0 && (
<View style={styles.counterBadge}>
<Text style={styles.counterText}>
{item.resultUrl?.length || 1}
</Text>
</View>
)}
{/* 状态角标 */}
{item.status === 'running' && (
<View style={styles.generatingBadge}>
<Text style={styles.generatingBadgeText}>{t('my.generating')}</Text>
</View>
)}
{item.status === 'pending' && (
<View style={styles.generatingBadge}>
<Text style={styles.generatingBadgeText}>{t('my.queuing')}</Text>
</View>
)}
</Pressable>
))}
{/* 加载更多指示器 */}
{loadingMore && (
<View style={styles.loadingMoreContainer}>
<ActivityIndicator size="small" color="#9966FF" />
</View>
)}
{/* 空状态提示 */}
{!loading && generations.length === 0 && (
<View style={styles.emptyState}>
<Text style={styles.emptyStateText}>
{t('my.noWorks')}
</Text>
</View>
)}
</View>
</ScrollView>
) : activeTab === 'favorites' && favoritesLoading ? (
<MySkeleton />
) : activeTab === 'favorites' ? (
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false}
>
<View style={styles.galleryGrid}>
{favorites.length === 0 ? (
<View style={styles.emptyState}> <View style={styles.emptyState}>
<Text style={styles.emptyStateText}> <Text style={styles.emptyStateText}>
{t('my.noFavorites')} {t('my.noFavorites')}
</Text> </Text>
</View> </View>
) : ( ) : (
favorites.map((item, index) => ( <>
<Pressable {favorites.map((item, index) => (
key={item.id} <Pressable
style={[ key={item.id}
styles.galleryItem, style={[
index % 3 !== 2 && styles.galleryItemMarginRight, styles.galleryItem,
styles.galleryItemMarginBottom, index % 3 !== 2 && styles.galleryItemMarginRight,
]} styles.galleryItemMarginBottom,
> ]}
<Image >
source={item.template?.coverImageUrl ? { uri: item.template.coverImageUrl } : require('@/assets/images/membership.png')} <Image
style={styles.galleryImage} source={item.template?.coverImageUrl ? { uri: item.template.coverImageUrl } : require('@/assets/images/membership.png')}
contentFit="cover" style={styles.galleryImage}
/> contentFit="cover"
</Pressable> />
)) </Pressable>
))}
{/* 加载更多指示器 */}
{favoritesLoadingMore && (
<View style={styles.loadingMoreContainer}>
<ActivityIndicator size="small" color="#9966FF" />
</View>
)}
</>
)} )}
</View> </View>
</ScrollView> </ScrollView>
@@ -425,48 +500,66 @@ export default function My() {
style={styles.scrollView} style={styles.scrollView}
contentContainerStyle={styles.scrollContent} contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
onScroll={handleLikesScroll}
scrollEventThrottle={16}
refreshControl={
<RefreshControl
refreshing={likesRefreshing}
onRefresh={onLikesRefresh}
tintColor="#9966FF"
colors={['#9966FF', '#FF6699', '#FF9966']}
progressBackgroundColor="#1C1E22"
progressViewOffset={10}
/>
}
> >
<View style={styles.galleryGrid}> <View style={styles.galleryGrid}>
{likes.length === 0 ? ( {likes.length === 0 && !likesLoading ? (
<View style={styles.emptyState}> <View style={styles.emptyState}>
<Text style={styles.emptyStateText}> <Text style={styles.emptyStateText}>
{t('my.noLikes')} {t('my.noLikes')}
</Text> </Text>
</View> </View>
) : ( ) : (
likes.map((item, index) => ( <>
<Pressable {likes.map((item, index) => (
key={item.id} <Pressable
style={[ key={item.id}
styles.galleryItem, style={[
index % 3 !== 2 && styles.galleryItemMarginRight, styles.galleryItem,
styles.galleryItemMarginBottom, index % 3 !== 2 && styles.galleryItemMarginRight,
]} styles.galleryItemMarginBottom,
> ]}
<Image >
source={item.template?.coverImageUrl ? { uri: item.template.coverImageUrl } : require('@/assets/images/membership.png')} <Image
style={styles.galleryImage} source={item.template?.coverImageUrl ? { uri: item.template.coverImageUrl } : require('@/assets/images/membership.png')}
contentFit="cover" style={styles.galleryImage}
/> contentFit="cover"
</Pressable> />
)) </Pressable>
))}
{/* 加载更多指示器 */}
{likesLoadingMore && (
<View style={styles.loadingMoreContainer}>
<ActivityIndicator size="small" color="#9966FF" />
</View>
)}
</>
)} )}
</View> </View>
</ScrollView> </ScrollView>
) : null} ) : null}
{/* 编辑资料抽屉 */} {/* 编辑资料抽屉 */}
<EditProfileDrawer
<EditProfileDrawer visible={editDrawerVisible}
visible={editDrawerVisible} onClose={() => setEditDrawerVisible(false)}
onClose={() => setEditDrawerVisible(false)} initialName={profileName}
initialName={profileName} initialAvatar={session?.user?.image}
initialAvatar={session?.user?.image} onSave={(data) => {
onSave={(data) => { setProfileName(data.name)
setProfileName(data.name) }}
}} />
/>
</KeyboardAwareScrollView>
</SafeAreaView> </SafeAreaView>
) )
} }
@@ -506,7 +599,7 @@ const styles = StyleSheet.create({
scrollContent: { scrollContent: {
backgroundColor: '#090A0B', backgroundColor: '#090A0B',
paddingHorizontal: GALLERY_HORIZONTAL_PADDING, paddingHorizontal: GALLERY_HORIZONTAL_PADDING,
paddingBottom: 20, paddingBottom: 100,
}, },
profileSection: { profileSection: {
flexDirection: 'row', flexDirection: 'row',

View File

@@ -112,9 +112,6 @@ export const MessageCard: React.FC<MessageCardProps> = ({
onDelete, onDelete,
}) => { }) => {
const config = getMessageConfig(message.data?.subType) const config = getMessageConfig(message.data?.subType)
const icon = getMessageIcon(message.data?.subType)
const showAvatar = config.showAvatar && message.data?.likerAvatar
const showPreview = config.showPreview && message.data?.webpPreviewUrl
const showQuote = config.showQuote && message.data?.originalComment const showQuote = config.showQuote && message.data?.originalComment
const handlePress = () => { const handlePress = () => {
@@ -148,15 +145,6 @@ export const MessageCard: React.FC<MessageCardProps> = ({
{message.content} {message.content}
</Text> </Text>
{/* Preview Image */}
{showPreview && (
<Image
testID="preview-image"
source={{ uri: message.data?.webpPreviewUrl }}
style={styles.previewImage}
/>
)}
{/* Quote */} {/* Quote */}
{showQuote && ( {showQuote && (
<View testID="quote-container" style={styles.quoteContainer}> <View testID="quote-container" style={styles.quoteContainer}>

View File

@@ -25,8 +25,8 @@ export const useMessages = (initialParams?: ListMessagesParams) => {
const [refreshing, setRefreshing] = useState(false) const [refreshing, setRefreshing] = useState(false)
const [error, setError] = useState<ApiError | null>(null) const [error, setError] = useState<ApiError | null>(null)
const [data, setData] = useState<ListMessagesResult>() const [data, setData] = useState<ListMessagesResult>()
const [hasMore, setHasMore] = useState(true)
const currentPageRef = useRef(1) const currentPageRef = useRef(1)
const hasMoreRef = useRef(true)
const execute = useCallback(async (params?: ListMessagesParams) => { const execute = useCallback(async (params?: ListMessagesParams) => {
setLoading(true) setLoading(true)
@@ -53,14 +53,15 @@ export const useMessages = (initialParams?: ListMessagesParams) => {
const currentPage = requestParams.page || 1 const currentPage = requestParams.page || 1
const totalPages = data?.totalPages || 1 const totalPages = data?.totalPages || 1
hasMoreRef.current = currentPage < totalPages const hasMoreValue = currentPage < totalPages
setHasMore(hasMoreValue)
setData(data) setData(data)
setLoading(false) setLoading(false)
return { data, error: null } return { data, error: null }
}, [initialParams]) }, [initialParams])
const loadMore = useCallback(async () => { const loadMore = useCallback(async () => {
if (loadingMore || loading || !hasMoreRef.current) return { data: undefined, error: null } if (loadingMore || loading || !hasMore) return { data: undefined, error: null }
setLoadingMore(true) setLoadingMore(true)
const nextPage = currentPageRef.current + 1 const nextPage = currentPageRef.current + 1
@@ -83,7 +84,8 @@ export const useMessages = (initialParams?: ListMessagesParams) => {
const newMessages = newData?.messages || [] const newMessages = newData?.messages || []
const totalPages = newData?.totalPages || 1 const totalPages = newData?.totalPages || 1
hasMoreRef.current = nextPage < totalPages const hasMoreValue = nextPage < totalPages
setHasMore(hasMoreValue)
currentPageRef.current = nextPage currentPageRef.current = nextPage
setData((prev) => ({ setData((prev) => ({
@@ -92,10 +94,10 @@ export const useMessages = (initialParams?: ListMessagesParams) => {
})) }))
setLoadingMore(false) setLoadingMore(false)
return { data: newData, error: null } return { data: newData, error: null }
}, [loading, loadingMore, initialParams]) }, [loading, loadingMore, hasMore, initialParams])
const refetch = useCallback(async () => { const refetch = useCallback(async () => {
hasMoreRef.current = true setHasMore(true)
setRefreshing(true) setRefreshing(true)
const result = await execute() const result = await execute()
setRefreshing(false) setRefreshing(false)
@@ -108,11 +110,11 @@ export const useMessages = (initialParams?: ListMessagesParams) => {
loading, loading,
loadingMore, loadingMore,
refreshing, refreshing,
hasMore,
error, error,
execute, execute,
refetch, refetch,
loadMore, loadMore,
hasMore: hasMoreRef.current,
} }
} }

View File

@@ -24,8 +24,8 @@ export const useUserFavorites = (initialParams?: GetUserFavoritesParams) => {
const [loadingMore, setLoadingMore] = useState(false) const [loadingMore, setLoadingMore] = useState(false)
const [error, setError] = useState<ApiError | null>(null) const [error, setError] = useState<ApiError | null>(null)
const [data, setData] = useState<GetUserFavoritesResponse>() const [data, setData] = useState<GetUserFavoritesResponse>()
const [hasMore, setHasMore] = useState(true)
const currentPageRef = useRef(1) const currentPageRef = useRef(1)
const hasMoreRef = useRef(true)
const execute = useCallback(async (params?: GetUserFavoritesParams) => { const execute = useCallback(async (params?: GetUserFavoritesParams) => {
setLoading(true) setLoading(true)
@@ -55,14 +55,15 @@ export const useUserFavorites = (initialParams?: GetUserFavoritesParams) => {
const total = data?.total || 0 const total = data?.total || 0
const limit = requestParams.limit || 20 const limit = requestParams.limit || 20
const totalPages = Math.ceil(total / limit) const totalPages = Math.ceil(total / limit)
hasMoreRef.current = currentPage < totalPages const hasMoreValue = currentPage < totalPages
setHasMore(hasMoreValue)
setData(data) setData(data)
setLoading(false) setLoading(false)
return { data, error: null } return { data, error: null }
}, [initialParams]) }, [initialParams])
const loadMore = useCallback(async () => { const loadMore = useCallback(async () => {
if (loadingMore || loading || !hasMoreRef.current) return { data: undefined, error: null } if (loadingMore || loading || !hasMore) return { data: undefined, error: null }
setLoadingMore(true) setLoadingMore(true)
const nextPage = currentPageRef.current + 1 const nextPage = currentPageRef.current + 1
@@ -87,7 +88,8 @@ export const useUserFavorites = (initialParams?: GetUserFavoritesParams) => {
const total = newData?.total || 0 const total = newData?.total || 0
const limit = requestParams.limit || 20 const limit = requestParams.limit || 20
const totalPages = Math.ceil(total / limit) const totalPages = Math.ceil(total / limit)
hasMoreRef.current = nextPage < totalPages const hasMoreValue = nextPage < totalPages
setHasMore(hasMoreValue)
currentPageRef.current = nextPage currentPageRef.current = nextPage
setData((prev) => ({ setData((prev) => ({
@@ -98,10 +100,10 @@ export const useUserFavorites = (initialParams?: GetUserFavoritesParams) => {
})) }))
setLoadingMore(false) setLoadingMore(false)
return { data: newData, error: null } return { data: newData, error: null }
}, [loading, loadingMore, initialParams]) }, [loading, loadingMore, hasMore, initialParams])
const refetch = useCallback(() => { const refetch = useCallback(() => {
hasMoreRef.current = true setHasMore(true)
return execute() return execute()
}, [execute]) }, [execute])
@@ -110,11 +112,11 @@ export const useUserFavorites = (initialParams?: GetUserFavoritesParams) => {
favorites: data?.favorites || [], favorites: data?.favorites || [],
loading, loading,
loadingMore, loadingMore,
hasMore,
error, error,
execute, execute,
refetch, refetch,
loadMore, loadMore,
hasMore: hasMoreRef.current,
} }
} }

View File

@@ -21,8 +21,8 @@ export const useUserLikes = (initialParams?: GetUserLikesParams) => {
const [loadingMore, setLoadingMore] = useState(false) const [loadingMore, setLoadingMore] = useState(false)
const [error, setError] = useState<ApiError | null>(null) const [error, setError] = useState<ApiError | null>(null)
const [data, setData] = useState<GetUserLikesResponse>() const [data, setData] = useState<GetUserLikesResponse>()
const [hasMore, setHasMore] = useState(true)
const currentPageRef = useRef(1) const currentPageRef = useRef(1)
const hasMoreRef = useRef(true)
const execute = useCallback(async (params?: GetUserLikesParams) => { const execute = useCallback(async (params?: GetUserLikesParams) => {
setLoading(true) setLoading(true)
@@ -52,14 +52,15 @@ export const useUserLikes = (initialParams?: GetUserLikesParams) => {
const total = data?.total || 0 const total = data?.total || 0
const limit = requestParams.limit || 20 const limit = requestParams.limit || 20
const totalPages = Math.ceil(total / limit) const totalPages = Math.ceil(total / limit)
hasMoreRef.current = currentPage < totalPages const hasMoreValue = currentPage < totalPages
setHasMore(hasMoreValue)
setData(data) setData(data)
setLoading(false) setLoading(false)
return { data, error: null } return { data, error: null }
}, [initialParams]) }, [initialParams])
const loadMore = useCallback(async () => { const loadMore = useCallback(async () => {
if (loadingMore || loading || !hasMoreRef.current) return { data: undefined, error: null } if (loadingMore || loading || !hasMore) return { data: undefined, error: null }
setLoadingMore(true) setLoadingMore(true)
const nextPage = currentPageRef.current + 1 const nextPage = currentPageRef.current + 1
@@ -84,7 +85,8 @@ export const useUserLikes = (initialParams?: GetUserLikesParams) => {
const total = newData?.total || 0 const total = newData?.total || 0
const limit = requestParams.limit || 20 const limit = requestParams.limit || 20
const totalPages = Math.ceil(total / limit) const totalPages = Math.ceil(total / limit)
hasMoreRef.current = nextPage < totalPages const hasMoreValue = nextPage < totalPages
setHasMore(hasMoreValue)
currentPageRef.current = nextPage currentPageRef.current = nextPage
setData((prev) => ({ setData((prev) => ({
@@ -93,10 +95,10 @@ export const useUserLikes = (initialParams?: GetUserLikesParams) => {
})) }))
setLoadingMore(false) setLoadingMore(false)
return { data: newData, error: null } return { data: newData, error: null }
}, [loading, loadingMore, initialParams]) }, [loading, loadingMore, hasMore, initialParams])
const refetch = useCallback(() => { const refetch = useCallback(() => {
hasMoreRef.current = true setHasMore(true)
return execute() return execute()
}, [execute]) }, [execute])
@@ -105,10 +107,10 @@ export const useUserLikes = (initialParams?: GetUserLikesParams) => {
likes: data?.likes || [], likes: data?.likes || [],
loading, loading,
loadingMore, loadingMore,
hasMore,
error, error,
execute, execute,
refetch, refetch,
loadMore, loadMore,
hasMore: hasMoreRef.current,
} }
} }