diff --git a/app/(tabs)/coach.tsx b/app/(tabs)/coach.tsx
index 1c80645..348c04b 100644
--- a/app/(tabs)/coach.tsx
+++ b/app/(tabs)/coach.tsx
@@ -34,8 +34,8 @@ import { loadAiCoachSessionCache, saveAiCoachSessionCache } from '@/services/aiC
import { api, getAuthToken, postTextStream } from '@/services/api';
import { selectLatestMoodRecordByDate } from '@/store/moodSlice';
import { generateWelcomeMessage, hasRecordedMoodToday } from '@/utils/welcomeMessage';
-import dayjs from 'dayjs';
import { LinearGradient } from 'expo-linear-gradient';
+import { HistoryModal } from '../../components/model/HistoryModal';
import { ActionSheet } from '../../components/ui/ActionSheet';
type Role = 'user' | 'assistant';
@@ -2099,52 +2099,16 @@ export default function CoachScreen() {
)}
- setHistoryVisible(false)}>
- setHistoryVisible(false)}>
-
-
- 历史会话
- refreshHistory(historyPage)} style={styles.modalRefreshBtn}>
-
-
-
- {historyLoading ? (
-
-
- 加载中...
-
- ) : (
-
- {historyItems.length === 0 ? (
- 暂无会话
- ) : (
- historyItems.map((it) => (
-
- handleSelectConversation(it.conversationId)}
- >
- {it.title || '未命名会话'}
-
- {dayjs(it.lastMessageAt || it.createdAt).format('YYYY/MM/DD HH:mm')}
-
-
- confirmDeleteConversation(it.conversationId)} style={styles.historyDeleteBtn}>
-
-
-
- ))
- )}
-
- )}
-
- setHistoryVisible(false)} style={styles.modalCloseBtn}>
- 关闭
-
-
-
-
-
+ setHistoryVisible(false)}
+ historyLoading={historyLoading}
+ historyItems={historyItems}
+ historyPage={historyPage}
+ onRefreshHistory={refreshHistory}
+ onSelectConversation={handleSelectConversation}
+ onDeleteConversation={confirmDeleteConversation}
+ />
setPreviewImageUri(null)}>
setPreviewImageUri(null)}>
@@ -2495,74 +2459,6 @@ const styles = StyleSheet.create({
shadowRadius: 4,
elevation: 2,
},
- modalBackdrop: {
- flex: 1,
- backgroundColor: 'rgba(0,0,0,0.35)',
- padding: 16,
- justifyContent: 'flex-end',
- },
- modalSheet: {
- borderTopLeftRadius: 16,
- borderTopRightRadius: 16,
- paddingHorizontal: 12,
- paddingTop: 10,
- paddingBottom: 12,
- },
- modalHeader: {
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'space-between',
- paddingHorizontal: 4,
- paddingBottom: 8,
- },
- modalTitle: {
- fontSize: 16,
- fontWeight: '800',
- color: '#192126',
- },
- modalRefreshBtn: {
- width: 28,
- height: 28,
- borderRadius: 14,
- alignItems: 'center',
- justifyContent: 'center',
- backgroundColor: 'rgba(0,0,0,0.06)'
- },
- historyRow: {
- flexDirection: 'row',
- alignItems: 'center',
- paddingVertical: 10,
- paddingHorizontal: 8,
- borderRadius: 10,
- },
- historyTitle: {
- fontSize: 15,
- color: '#192126',
- fontWeight: '600',
- },
- historyMeta: {
- marginTop: 2,
- fontSize: 12,
- color: '#687076',
- },
- historyDeleteBtn: {
- width: 28,
- height: 28,
- borderRadius: 14,
- alignItems: 'center',
- justifyContent: 'center',
- backgroundColor: 'rgba(255,68,68,0.08)'
- },
- modalFooter: {
- paddingTop: 8,
- alignItems: 'flex-end',
- },
- modalCloseBtn: {
- paddingHorizontal: 14,
- paddingVertical: 8,
- borderRadius: 10,
- backgroundColor: 'rgba(0,0,0,0.06)'
- },
previewBackdrop: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.85)',
diff --git a/app/nutrition/records.tsx b/app/nutrition/records.tsx
index 337118f..143ac15 100644
--- a/app/nutrition/records.tsx
+++ b/app/nutrition/records.tsx
@@ -1,6 +1,7 @@
import { NutritionRecordCard } from '@/components/NutritionRecordCard';
import { HeaderBar } from '@/components/ui/HeaderBar';
import { Colors } from '@/constants/Colors';
+import { DateSelector } from '@/components/DateSelector';
import { useColorScheme } from '@/hooks/useColorScheme';
import { DietRecord, deleteDietRecord, getDietRecords } from '@/services/dietRecords';
import { getMonthDaysZh, getMonthTitleZh, getTodayIndexInMonth } from '@/utils/date';
@@ -190,73 +191,12 @@ export default function NutritionRecordsScreen() {
if (viewMode !== 'daily') return null;
return (
-
- setScrollWidth(e.nativeEvent.layout.width)}
- >
- {days.map((day, index) => {
- const isSelected = index === selectedIndex;
- const isToday = day.isToday;
- const isDisabled = day.date?.isAfter(dayjs(), 'day') ?? false;
-
- return (
- {
- if (!isDisabled) {
- setSelectedIndex(index);
- }
- }}
- disabled={isDisabled}
- >
-
- {day.date?.date() ?? ''}
-
-
- {day.dayAbbr}
-
-
- );
- })}
-
-
+ setSelectedIndex(index)}
+ showMonthTitle={false}
+ disableFutureDates={true}
+ />
);
};
diff --git a/components/NutritionRecordCard.tsx b/components/NutritionRecordCard.tsx
index cb640bd..fd4c523 100644
--- a/components/NutritionRecordCard.tsx
+++ b/components/NutritionRecordCard.tsx
@@ -1,11 +1,11 @@
import { ThemedText } from '@/components/ThemedText';
-import { ActionSheet } from '@/components/ui/ActionSheet';
import { useThemeColor } from '@/hooks/useThemeColor';
import { DietRecord } from '@/services/dietRecords';
import { Ionicons } from '@expo/vector-icons';
import dayjs from 'dayjs';
-import React, { useMemo, useState } from 'react';
+import React, { useMemo, useState, useRef } from 'react';
import { Image, StyleSheet, TouchableOpacity, View } from 'react-native';
+import Popover from 'react-native-popover-view';
export type NutritionRecordCardProps = {
record: DietRecord;
@@ -53,8 +53,9 @@ export function NutritionRecordCard({
const textSecondaryColor = useThemeColor({}, 'textSecondary');
const primaryColor = useThemeColor({}, 'primary');
- // ActionSheet 状态管理
- const [showActionSheet, setShowActionSheet] = useState(false);
+ // Popover 状态管理
+ const [showPopover, setShowPopover] = useState(false);
+ const popoverRef = useRef(null);
// 营养数据统计
const nutritionStats = useMemo(() => {
@@ -112,31 +113,32 @@ export function NutritionRecordCard({
)}
{/* 卡片内容 */}
-
{/* 主要内容区域 */}
- {/* 餐次和操作按钮 */}
-
+
{!showTimeline && (
{record.mealTime ? dayjs(record.mealTime).format('HH:mm') : '时间未设置'}
)}
- setShowActionSheet(true)}
+ onPress={() => {
+ setShowPopover(true)
+ console.log('showPopover', showPopover)
+ }}
>
@@ -144,14 +146,14 @@ export function NutritionRecordCard({
{/* 食物名称和分量 */}
- {/* 左侧:食物图片 */}
-
- {record.imageUrl ? (
-
- ) : (
-
- )}
-
+ {/* 左侧:食物图片 */}
+
+ {record.imageUrl ? (
+
+ ) : (
+
+ )}
+
{record.foodName}
@@ -161,11 +163,11 @@ export function NutritionRecordCard({
{record.portionDescription || `${record.weightGrams}g`}
)}
-
-
- {mealTypeLabel}
-
-
+
+
+ {mealTypeLabel}
+
+
{/* 营养信息 - 紧凑标签布局 */}
@@ -193,26 +195,31 @@ export function NutritionRecordCard({
)}
-
+
- {/* ActionSheet for more options */}
- setShowActionSheet(false)}
- title="选择操作"
- options={[
- {
- id: 'delete',
- title: '删除记录',
- subtitle: '删除后无法恢复',
- icon: 'trash-outline',
- destructive: true,
- onPress: () => {
+ {/* Popover for more options */}
+ setShowPopover(false)}
+ popoverStyle={styles.popoverContainer}
+ backgroundStyle={styles.popoverBackground}
+ >
+
+ {
+ setShowPopover(false);
onDelete?.();
- }
- }
- ]}
- />
+ }}
+ >
+
+
+ 删除记录
+
+
+
+
);
}
@@ -300,6 +307,7 @@ const styles = StyleSheet.create({
position: 'absolute',
top: 0,
right: 0,
+ zIndex: 1,
},
mealTypeContainer: {
flex: 1,
@@ -320,6 +328,7 @@ const styles = StyleSheet.create({
moreButton: {
padding: 4,
marginLeft: 8,
+ zIndex: 1,
},
foodNameSection: {
marginBottom: 12,
@@ -379,4 +388,30 @@ const styles = StyleSheet.create({
lineHeight: 18,
fontStyle: 'italic',
},
+ popoverContainer: {
+ borderRadius: 12,
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 2 },
+ shadowOpacity: 0.1,
+ shadowRadius: 8,
+ elevation: 5,
+ },
+ popoverBackground: {
+ backgroundColor: 'rgba(0, 0, 0, 0.3)',
+ },
+ popoverContent: {
+ minWidth: 140,
+ paddingVertical: 8,
+ },
+ popoverItem: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingHorizontal: 16,
+ paddingVertical: 12,
+ gap: 12,
+ },
+ popoverText: {
+ fontSize: 16,
+ fontWeight: '500',
+ },
});
diff --git a/components/model/HistoryModal.tsx b/components/model/HistoryModal.tsx
new file mode 100644
index 0000000..59ac333
--- /dev/null
+++ b/components/model/HistoryModal.tsx
@@ -0,0 +1,170 @@
+import { Ionicons } from '@expo/vector-icons';
+import React from 'react';
+import {
+ ActivityIndicator,
+ Modal,
+ ScrollView,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+} from 'react-native';
+import dayjs from 'dayjs';
+
+import { AiConversationListItem } from '@/services/aiCoach';
+
+interface HistoryModalProps {
+ visible: boolean;
+ onClose: () => void;
+ historyLoading: boolean;
+ historyItems: AiConversationListItem[];
+ historyPage: number;
+ onRefreshHistory: (page: number) => void;
+ onSelectConversation: (id: string) => void;
+ onDeleteConversation: (id: string) => void;
+}
+
+export const HistoryModal: React.FC = ({
+ visible,
+ onClose,
+ historyLoading,
+ historyItems,
+ historyPage,
+ onRefreshHistory,
+ onSelectConversation,
+ onDeleteConversation,
+}) => {
+ return (
+
+
+
+
+ 历史会话
+ onRefreshHistory(historyPage)}
+ style={styles.modalRefreshBtn}
+ >
+
+
+
+ {historyLoading ? (
+
+
+ 加载中...
+
+ ) : (
+
+ {historyItems.length === 0 ? (
+ 暂无会话
+ ) : (
+ historyItems.map((it) => (
+
+ onSelectConversation(it.conversationId)}
+ >
+ {it.title || '未命名会话'}
+
+ {dayjs(it.lastMessageAt || it.createdAt).format('YYYY/MM/DD HH:mm')}
+
+
+ onDeleteConversation(it.conversationId)}
+ style={styles.historyDeleteBtn}
+ >
+
+
+
+ ))
+ )}
+
+ )}
+
+
+ 关闭
+
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ modalBackdrop: {
+ flex: 1,
+ backgroundColor: 'rgba(0,0,0,0.35)',
+ padding: 16,
+ justifyContent: 'flex-end',
+ },
+ modalSheet: {
+ borderTopLeftRadius: 16,
+ borderTopRightRadius: 16,
+ paddingHorizontal: 12,
+ paddingTop: 10,
+ paddingBottom: 12,
+ },
+ modalHeader: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ paddingHorizontal: 4,
+ paddingBottom: 8,
+ },
+ modalTitle: {
+ fontSize: 16,
+ fontWeight: '800',
+ color: '#192126',
+ },
+ modalRefreshBtn: {
+ width: 28,
+ height: 28,
+ borderRadius: 14,
+ alignItems: 'center',
+ justifyContent: 'center',
+ backgroundColor: 'rgba(0,0,0,0.06)'
+ },
+ historyRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingVertical: 10,
+ paddingHorizontal: 8,
+ borderRadius: 10,
+ },
+ historyTitle: {
+ fontSize: 15,
+ color: '#192126',
+ fontWeight: '600',
+ },
+ historyMeta: {
+ marginTop: 2,
+ fontSize: 12,
+ color: '#687076',
+ },
+ historyDeleteBtn: {
+ width: 28,
+ height: 28,
+ borderRadius: 14,
+ alignItems: 'center',
+ justifyContent: 'center',
+ backgroundColor: 'rgba(255,68,68,0.08)'
+ },
+ modalFooter: {
+ paddingTop: 8,
+ alignItems: 'flex-end',
+ },
+ modalCloseBtn: {
+ paddingHorizontal: 14,
+ paddingVertical: 8,
+ borderRadius: 10,
+ backgroundColor: 'rgba(0,0,0,0.06)'
+ },
+});
+
+export default HistoryModal;
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index a716122..ccc83f8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -49,6 +49,7 @@
"react-native-image-viewing": "^0.2.2",
"react-native-markdown-display": "^7.0.2",
"react-native-modal-datetime-picker": "^18.0.0",
+ "react-native-popover-view": "^6.1.0",
"react-native-purchases": "^9.2.2",
"react-native-reanimated": "~3.17.4",
"react-native-render-html": "^6.3.4",
@@ -6114,6 +6115,17 @@
"node": ">= 0.8"
}
},
+ "node_modules/deprecated-react-native-prop-types": {
+ "version": "2.3.0",
+ "resolved": "https://mirrors.tencent.com/npm/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-2.3.0.tgz",
+ "integrity": "sha512-pWD0voFtNYxrVqvBMYf5gq3NA2GCpfodS1yNynTPc93AYA/KEMGeWDqqeUB6R2Z9ZofVhks2aeJXiuQqKNpesA==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-native/normalize-color": "*",
+ "invariant": "*",
+ "prop-types": "*"
+ }
+ },
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
@@ -11620,6 +11632,16 @@
"react-native": ">=0.65.0"
}
},
+ "node_modules/react-native-popover-view": {
+ "version": "6.1.0",
+ "resolved": "https://mirrors.tencent.com/npm/react-native-popover-view/-/react-native-popover-view-6.1.0.tgz",
+ "integrity": "sha512-j1CB+yPwTKlBvIJBNb1AwiHyF/r+W5+AJIbHk79GRa+0z6PVtW4C7NWJWPqUVkCMOcJtewl6Pr6f2dc/87cVyQ==",
+ "license": "MIT",
+ "dependencies": {
+ "deprecated-react-native-prop-types": "^2.3.0",
+ "prop-types": "^15.8.1"
+ }
+ },
"node_modules/react-native-purchases": {
"version": "9.2.2",
"resolved": "https://mirrors.tencent.com/npm/react-native-purchases/-/react-native-purchases-9.2.2.tgz",
diff --git a/package.json b/package.json
index a04d3cb..79ff08f 100644
--- a/package.json
+++ b/package.json
@@ -53,6 +53,7 @@
"react-native-image-viewing": "^0.2.2",
"react-native-markdown-display": "^7.0.2",
"react-native-modal-datetime-picker": "^18.0.0",
+ "react-native-popover-view": "^6.1.0",
"react-native-purchases": "^9.2.2",
"react-native-reanimated": "~3.17.4",
"react-native-render-html": "^6.3.4",