feat: implement pull-to-refresh and load more functionality in "My" page, add WebP image support

This commit is contained in:
imeepos 2026-01-27 17:08:11 +08:00
parent c78ad352ba
commit 8f00d4644a
2 changed files with 62 additions and 8 deletions

2
.gitignore vendored
View File

@ -11,7 +11,7 @@ expo-env.d.ts
tmpclaude-*
*empclaude*
tmpclaude-*
*tmpclaude-*
# Native
.kotlin/

View File

@ -7,6 +7,10 @@ import {
Dimensions,
Pressable,
StatusBar as RNStatusBar,
RefreshControl,
ActivityIndicator,
NativeScrollEvent,
NativeSyntheticEvent,
} from 'react-native'
import { StatusBar } from 'expo-status-bar'
import { SafeAreaView } from 'react-native-safe-area-context'
@ -17,10 +21,10 @@ import { PointsIcon, SearchIcon, SettingsIcon } from '@/components/icon'
import EditProfileDrawer from '@/components/drawer/EditProfileDrawer'
import Dropdown from '@/components/ui/dropdown'
import Toast from '@/components/ui/Toast'
import { signOut } from '@/lib/auth'
import { signOut , useSession } from '@/lib/auth'
import { useTemplateGenerations, type TemplateGeneration } from '@/hooks'
import { MySkeleton } from '@/components/skeleton/MySkeleton'
import { useSession } from '@/lib/auth'
import { useUserBalance } from '@/hooks/use-user-balance'
const { width: screenWidth } = Dimensions.get('window')
@ -30,9 +34,9 @@ const GALLERY_ITEM_SIZE = Math.floor(
(screenWidth - GALLERY_HORIZONTAL_PADDING * 2 - GALLERY_GAP * 2) / 3
)
// 获取作品封面图
// 获取作品封面图 - Webp优先
const getCoverUrl = (item: TemplateGeneration) =>
item.resultUrl?.[0] || item.template?.coverImageUrl
item.webpPreviewUrl || item.resultUrl?.[0] || item.template?.coverImageUrl
export default function My() {
const router = useRouter()
@ -58,16 +62,41 @@ export default function My() {
const {
generations,
loading,
error,
execute: loadGenerations,
loadingMore,
refetch,
loadMore,
hasMore,
} = useTemplateGenerations()
// 初始化加载作品列表
useEffect(() => {
loadGenerations({ page: 1, limit: 50 })
refetch({ page: 1, limit: 50 })
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
// 下拉刷新状态
const [refreshing, setRefreshing] = useState(false)
// 下拉刷新处理
const onRefresh = useCallback(async () => {
setRefreshing(true)
await refetch({ page: 1, limit: 50 })
setRefreshing(false)
}, [refetch])
// 加载更多处理
const handleEndReached = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => {
const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent
const paddingToBottom = 100 // 距离底部100px时触发加载更多
if (
layoutMeasurement.height + contentOffset.y >= contentSize.height - paddingToBottom &&
!loadingMore &&
hasMore
) {
loadMore()
}
}, [loadingMore, hasMore, loadMore])
// 处理设置菜单选择
const handleSettingsSelect = async (value: string) => {
if (value === 'changePassword') {
@ -193,6 +222,18 @@ export default function My() {
style={styles.scrollView}
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false}
onScroll={handleEndReached}
scrollEventThrottle={400}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
tintColor="#9966FF"
colors={['#9966FF', '#FF6699', '#FF9966']}
progressBackgroundColor="#1C1E22"
progressViewOffset={10}
/>
}
>
<View style={styles.galleryGrid}>
{generations.map((item, index) => (
@ -247,6 +288,13 @@ export default function My() {
</Pressable>
))}
{/* 加载更多指示器 */}
{loadingMore && (
<View style={styles.loadingMoreContainer}>
<ActivityIndicator size="small" color="#9966FF" />
</View>
)}
{/* 空状态提示 */}
{!loading && generations.length === 0 && (
<View style={styles.emptyState}>
@ -419,6 +467,12 @@ const styles = StyleSheet.create({
fontSize: 9,
fontWeight: '500',
},
loadingMoreContainer: {
width: '100%',
paddingVertical: 20,
alignItems: 'center',
justifyContent: 'center',
},
emptyState: {
width: '100%',
alignItems: 'center',