diff --git a/app/(tabs)/message.tsx b/app/(tabs)/message.tsx index 9bcacc0..b31edce 100644 --- a/app/(tabs)/message.tsx +++ b/app/(tabs)/message.tsx @@ -6,87 +6,198 @@ import { ScrollView, StatusBar as RNStatusBar, Pressable, + RefreshControl, + NativeScrollEvent, + NativeSyntheticEvent, } from 'react-native' import { LinearGradient } from 'expo-linear-gradient' import { StatusBar } from 'expo-status-bar' import { SafeAreaView } from 'react-native-safe-area-context' import { useTranslation } from 'react-i18next' +import { useMessages, type Message } from '@/hooks/use-messages' +import { useMessageActions } from '@/hooks/use-message-actions' +import LoadingState from '@/components/LoadingState' +import ErrorState from '@/components/ErrorState' +import PaginationLoader from '@/components/PaginationLoader' -// 消息卡片数据 -interface MessageCard { - id: number - title: string - subtitle: string - body: string - time: string - type: 'notice' | 'other' - isNew?: boolean // 是否为新消息 +const getMessageTypeByTab = (tab: 'all' | 'notice' | 'other') => { + if (tab === 'all') return undefined + if (tab === 'notice') return ['SYSTEM', 'ACTIVITY'] + return ['BILLING', 'MARKETING'] } -const messageCards: MessageCard[] = [ - { - id: 1, - title: '恭喜你,获得双12新用户专享福利', - subtitle: '打开推送的内容,限时优惠,多种玩法,快来体验~', - body: '图片占位。', - time: '2023-12-29 18:32:21', - type: 'notice', - isNew: true, // 新消息 - }, - { - id: 2, - title: '新功能上线:AI 智能生成', - subtitle: '全新 AI 功能已上线,快来体验吧!', - body: '图片占位。', - time: '2023-12-28 15:20:10', - type: 'notice', - isNew: true, // 新消息 - }, - { - id: 3, - title: '系统维护通知', - subtitle: '系统将于今晚进行维护升级', - body: '图片占位。', - time: '2023-12-27 10:15:30', - type: 'other', - isNew: false, // 非新消息 - }, - { - id: 4, - title: '限时活动:分享有礼', - subtitle: '分享你的作品,赢取丰厚奖励', - body: '图片占位。', - time: '2023-12-26 14:05:22', - type: 'notice', - isNew: false, // 非新消息 - }, - { - id: 5, - title: '版本更新提醒', - subtitle: '新版本已发布,建议及时更新', - body: '图片占位。', - time: '2023-12-25 09:30:45', - type: 'other', - isNew: false, // 非新消息 - }, -] +const formatTime = (date: Date) => { + const d = new Date(date) + const year = d.getFullYear() + const month = String(d.getMonth() + 1).padStart(2, '0') + const day = String(d.getDate()).padStart(2, '0') + const hours = String(d.getHours()).padStart(2, '0') + const minutes = String(d.getMinutes()).padStart(2, '0') + const seconds = String(d.getSeconds()).padStart(2, '0') + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}` +} export default function MessageScreen() { const { t } = useTranslation() const [activeTab, setActiveTab] = useState<'all' | 'notice' | 'other'>('all') + const { markRead } = useMessageActions() - // 根据选中的标签过滤卡片 - const filteredCards = messageCards.filter((card) => { - if (activeTab === 'all') return true - return card.type === activeTab + const messageType = getMessageTypeByTab(activeTab) + const { messages, loading, loadingMore, error, refetch, loadMore, hasMore, execute } = useMessages({ + limit: 20, }) + useEffect(() => { + execute({ type: messageType as any }) + }, [activeTab]) + + const handleScroll = (event: NativeSyntheticEvent) => { + const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent + const isCloseToBottom = layoutMeasurement.height + contentOffset.y >= contentSize.height - 50 + + if (isCloseToBottom && hasMore && !loadingMore && !loading) { + loadMore() + } + } + + const handleMessagePress = async (message: Message) => { + if (!message.isRead) { + await markRead(message.id) + refetch() + } + } + + if (loading && messages.length === 0) { + return ( + + + + + setActiveTab('all')}> + {activeTab === 'all' ? ( + + + + {t('message.all')} + + + ) : ( + {t('message.all')} + )} + + setActiveTab('notice')}> + {activeTab === 'notice' ? ( + + + + {t('message.notice')} + + + ) : ( + {t('message.notice')} + )} + + setActiveTab('other')}> + {activeTab === 'other' ? ( + + + + {t('message.other')} + + + ) : ( + {t('message.other')} + )} + + + + + ) + } + + if (error && messages.length === 0) { + return ( + + + + + setActiveTab('all')}> + {activeTab === 'all' ? ( + + + + {t('message.all')} + + + ) : ( + {t('message.all')} + )} + + setActiveTab('notice')}> + {activeTab === 'notice' ? ( + + + + {t('message.notice')} + + + ) : ( + {t('message.notice')} + )} + + setActiveTab('other')}> + {activeTab === 'other' ? ( + + + + {t('message.other')} + + + ) : ( + {t('message.other')} + )} + + + + + ) + } + return ( - {/* 固定在顶部的标签选择器 */} setActiveTab('all')}> {activeTab === 'all' ? ( @@ -156,45 +267,57 @@ export default function MessageScreen() { - {/* 可滚动的消息卡片列表 */} 0} + onRefresh={refetch} + tintColor="#FFFFFF" + /> + } > - {/* 消息卡片列表 */} - {filteredCards.length > 0 ? ( - filteredCards.map((card) => ( - - {/* 新消息绿色指示点 */} - {card.isNew && ( - - + {messages.length > 0 ? ( + <> + {messages.map((message) => ( + handleMessagePress(message)} + > + + {!message.isRead && ( + + + + )} + + {message.title} + + + {message.content} + + + + + {message.data || ''} + + + + + {formatTime(message.createdAt)} + - )} - - {card.title} - - - {card.subtitle} - - - - - {/* TODO:这里是图片 */} - {card.body} - - - - - {card.time} - - - )) + + ))} + {loadingMore && } + ) : ( - {/* */} - 💭 + 💭 {t('message.noMessages')} )} @@ -216,12 +339,6 @@ const styles = StyleSheet.create({ paddingHorizontal: 4, paddingTop: 12, }, - appMiniIcon: { - width: 28, - height: 18, - borderRadius: 4, - backgroundColor: '#FF66AA', - }, segment: { flexDirection: 'row', paddingHorizontal: 8, @@ -230,7 +347,6 @@ const styles = StyleSheet.create({ backgroundColor: '#090A0B', gap: 16, }, - segmentText: { fontSize: 14, color: '#FFFFFF', @@ -252,20 +368,13 @@ const styles = StyleSheet.create({ segmentTextActiveText: { zIndex: 1, }, - statusDot: { - width: 6, - height: 6, - borderRadius: 3, - backgroundColor: '#00FF66', - marginRight: 4, - }, cardContainer: { backgroundColor: '#16181B', borderRadius: 16, padding: 16, marginBottom: 12, marginHorizontal: 4, - position: 'relative', // 用于定位新消息指示点 + position: 'relative', }, newMessageDotContainer: { position: 'absolute', @@ -283,7 +392,7 @@ const styles = StyleSheet.create({ width: 6, height: 6, borderRadius: 4, - backgroundColor: '#00FF66', // 绿色 + backgroundColor: '#00FF66', zIndex: 1, }, cardTitle: { @@ -300,8 +409,9 @@ const styles = StyleSheet.create({ cardBody: { backgroundColor: '#26292E', borderRadius: 12, - height:100, + height: 100, marginBottom: 12, + padding: 8, }, cardBodyText: { color: '#FFFFFF', @@ -318,6 +428,9 @@ const styles = StyleSheet.create({ justifyContent: 'center', paddingTop: 200, }, + emptyIcon: { + fontSize: 48, + }, emptyText: { color: '#8A8A8A', fontSize: 12, diff --git a/hooks/use-messages.ts b/hooks/use-messages.ts index 719dec2..2fd5bbc 100644 --- a/hooks/use-messages.ts +++ b/hooks/use-messages.ts @@ -9,10 +9,14 @@ import { handleError } from './use-error' interface ListMessagesParams { page?: number limit?: number + type?: ('SYSTEM' | 'ACTIVITY' | 'BILLING' | 'MARKETING')[] + isRead?: boolean } const DEFAULT_PARAMS = { limit: 20, + orderBy: 'createdAt', + order: 'desc', } export const useMessages = (initialParams?: ListMessagesParams) => {