feat: implement pull-to-refresh and load more functionality in "My" page, add WebP image support
This commit is contained in:
parent
c78ad352ba
commit
8f00d4644a
|
|
@ -11,7 +11,7 @@ expo-env.d.ts
|
||||||
tmpclaude-*
|
tmpclaude-*
|
||||||
|
|
||||||
*empclaude*
|
*empclaude*
|
||||||
tmpclaude-*
|
*tmpclaude-*
|
||||||
|
|
||||||
# Native
|
# Native
|
||||||
.kotlin/
|
.kotlin/
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue