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-* tmpclaude-*
*empclaude* *empclaude*
tmpclaude-* *tmpclaude-*
# Native # Native
.kotlin/ .kotlin/

View File

@ -7,6 +7,10 @@ import {
Dimensions, Dimensions,
Pressable, Pressable,
StatusBar as RNStatusBar, StatusBar as RNStatusBar,
RefreshControl,
ActivityIndicator,
NativeScrollEvent,
NativeSyntheticEvent,
} 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'
@ -17,10 +21,10 @@ import { PointsIcon, SearchIcon, SettingsIcon } from '@/components/icon'
import EditProfileDrawer from '@/components/drawer/EditProfileDrawer' import EditProfileDrawer from '@/components/drawer/EditProfileDrawer'
import Dropdown from '@/components/ui/dropdown' import Dropdown from '@/components/ui/dropdown'
import Toast from '@/components/ui/Toast' import Toast from '@/components/ui/Toast'
import { signOut } from '@/lib/auth' import { signOut , useSession } from '@/lib/auth'
import { useTemplateGenerations, type TemplateGeneration } from '@/hooks' import { useTemplateGenerations, type TemplateGeneration } from '@/hooks'
import { MySkeleton } from '@/components/skeleton/MySkeleton' import { MySkeleton } from '@/components/skeleton/MySkeleton'
import { useSession } from '@/lib/auth'
import { useUserBalance } from '@/hooks/use-user-balance' import { useUserBalance } from '@/hooks/use-user-balance'
const { width: screenWidth } = Dimensions.get('window') 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 (screenWidth - GALLERY_HORIZONTAL_PADDING * 2 - GALLERY_GAP * 2) / 3
) )
// 获取作品封面图 // 获取作品封面图 - Webp优先
const getCoverUrl = (item: TemplateGeneration) => const getCoverUrl = (item: TemplateGeneration) =>
item.resultUrl?.[0] || item.template?.coverImageUrl item.webpPreviewUrl || item.resultUrl?.[0] || item.template?.coverImageUrl
export default function My() { export default function My() {
const router = useRouter() const router = useRouter()
@ -58,16 +62,41 @@ export default function My() {
const { const {
generations, generations,
loading, loading,
error, loadingMore,
execute: loadGenerations,
refetch, refetch,
loadMore,
hasMore,
} = useTemplateGenerations() } = useTemplateGenerations()
// 初始化加载作品列表 // 初始化加载作品列表
useEffect(() => { 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) => { const handleSettingsSelect = async (value: string) => {
if (value === 'changePassword') { if (value === 'changePassword') {
@ -193,6 +222,18 @@ export default function My() {
style={styles.scrollView} style={styles.scrollView}
contentContainerStyle={styles.scrollContent} contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false} 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}> <View style={styles.galleryGrid}>
{generations.map((item, index) => ( {generations.map((item, index) => (
@ -247,6 +288,13 @@ export default function My() {
</Pressable> </Pressable>
))} ))}
{/* 加载更多指示器 */}
{loadingMore && (
<View style={styles.loadingMoreContainer}>
<ActivityIndicator size="small" color="#9966FF" />
</View>
)}
{/* 空状态提示 */} {/* 空状态提示 */}
{!loading && generations.length === 0 && ( {!loading && generations.length === 0 && (
<View style={styles.emptyState}> <View style={styles.emptyState}>
@ -419,6 +467,12 @@ const styles = StyleSheet.create({
fontSize: 9, fontSize: 9,
fontWeight: '500', fontWeight: '500',
}, },
loadingMoreContainer: {
width: '100%',
paddingVertical: 20,
alignItems: 'center',
justifyContent: 'center',
},
emptyState: { emptyState: {
width: '100%', width: '100%',
alignItems: 'center', alignItems: 'center',