fix: bug
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user