feat: AI Agent 处理器扩展 + 服药打卡确认链路 + AI 气泡配色微调 + 数据刷新 providers 抽离 + 多端页面细节调整
This commit is contained in:
@@ -292,13 +292,7 @@ class _TrendPageState extends ConsumerState<TrendPage> {
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
builder: (ctx) => Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(ctx).viewInsets.bottom,
|
||||
left: 20,
|
||||
right: 20,
|
||||
top: 20,
|
||||
),
|
||||
builder: (ctx) => _KeyboardInsetPadding(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@@ -415,6 +409,7 @@ class _TrendPageState extends ConsumerState<TrendPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GradientScaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
@@ -1304,6 +1299,25 @@ class _TrendPageState extends ConsumerState<TrendPage> {
|
||||
}
|
||||
}
|
||||
|
||||
class _KeyboardInsetPadding extends StatelessWidget {
|
||||
final Widget child;
|
||||
|
||||
const _KeyboardInsetPadding({required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.viewInsetsOf(context).bottom,
|
||||
left: 20,
|
||||
right: 20,
|
||||
top: 20,
|
||||
),
|
||||
child: RepaintBoundary(child: child),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _StatisticItem extends StatelessWidget {
|
||||
final String label;
|
||||
final String value;
|
||||
|
||||
@@ -8,6 +8,7 @@ import '../../core/app_colors.dart';
|
||||
import '../../core/app_design_tokens.dart';
|
||||
import '../../core/app_theme.dart';
|
||||
import '../../providers/auth_provider.dart';
|
||||
import '../../providers/data_refresh_providers.dart';
|
||||
import '../../widgets/app_toast.dart';
|
||||
import '../../widgets/common_widgets.dart';
|
||||
import '../../widgets/app_empty_state.dart';
|
||||
@@ -803,6 +804,7 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
|
||||
Future<void> _saveDietRecord() async {
|
||||
try {
|
||||
await ref.read(dietProvider.notifier).saveRecord();
|
||||
ref.read(dietDataRefreshSignalProvider.notifier).trigger();
|
||||
if (mounted) popRoute(ref);
|
||||
} on DietFoodValidationException catch (e) {
|
||||
if (mounted) {
|
||||
|
||||
@@ -3,11 +3,13 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../core/app_colors.dart';
|
||||
import '../../core/navigation_provider.dart';
|
||||
import '../../providers/auth_provider.dart';
|
||||
import '../../providers/backoffice_refresh_providers.dart';
|
||||
import '../../widgets/backoffice_ui.dart';
|
||||
import '../doctor/doctor_home_page.dart' show doctorPageProvider;
|
||||
|
||||
final _dashboardProvider = FutureProvider<Map<String, dynamic>?>((ref) async {
|
||||
ref.watch(userSessionIdentityProvider);
|
||||
ref.watch(doctorDashboardRefreshSignalProvider);
|
||||
final api = ref.read(apiClientProvider);
|
||||
final res = await api.get('/api/doctor/dashboard');
|
||||
return res.data['data'] as Map<String, dynamic>?;
|
||||
|
||||
@@ -274,6 +274,7 @@ class _DoctorFollowUpEditPageState
|
||||
}
|
||||
if (mounted) {
|
||||
ref.read(doctorFollowupsRefreshSignalProvider.notifier).trigger();
|
||||
ref.read(doctorDashboardRefreshSignalProvider.notifier).trigger();
|
||||
_snack(isEdit ? '修改成功' : '创建成功', ok: true);
|
||||
popRoute(ref);
|
||||
}
|
||||
|
||||
@@ -211,6 +211,12 @@ class _DoctorFollowupsPageState extends ConsumerState<DoctorFollowupsPage> {
|
||||
.markDone(
|
||||
f['id']?.toString() ?? '',
|
||||
);
|
||||
ref
|
||||
.read(
|
||||
doctorDashboardRefreshSignalProvider
|
||||
.notifier,
|
||||
)
|
||||
.trigger();
|
||||
},
|
||||
child: const Text(
|
||||
'完成',
|
||||
@@ -232,6 +238,12 @@ class _DoctorFollowupsPageState extends ConsumerState<DoctorFollowupsPage> {
|
||||
.remove(
|
||||
f['id']?.toString() ?? '',
|
||||
);
|
||||
ref
|
||||
.read(
|
||||
doctorDashboardRefreshSignalProvider
|
||||
.notifier,
|
||||
)
|
||||
.trigger();
|
||||
},
|
||||
child: const Text(
|
||||
'删除',
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../core/app_colors.dart';
|
||||
import '../../core/navigation_provider.dart';
|
||||
import '../../providers/auth_provider.dart';
|
||||
import '../../providers/backoffice_refresh_providers.dart';
|
||||
import '../../widgets/app_toast.dart';
|
||||
import '../../widgets/backoffice_ui.dart';
|
||||
|
||||
@@ -120,6 +121,7 @@ class _DoctorProfileEditPageState extends ConsumerState<DoctorProfileEditPage> {
|
||||
},
|
||||
);
|
||||
ref.invalidate(_docProfileProvider);
|
||||
ref.read(doctorDashboardRefreshSignalProvider.notifier).trigger();
|
||||
if (mounted) {
|
||||
AppToast.show(context, '保存成功', type: AppToastType.success);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../core/app_colors.dart';
|
||||
import '../../core/navigation_provider.dart';
|
||||
import '../../providers/auth_provider.dart';
|
||||
import '../../providers/backoffice_refresh_providers.dart';
|
||||
import '../../utils/backoffice_formatters.dart';
|
||||
import '../../widgets/app_toast.dart';
|
||||
import '../../widgets/backoffice_ui.dart';
|
||||
@@ -75,6 +76,8 @@ class _DoctorReportDetailPageState
|
||||
if (!mounted) return;
|
||||
setState(() => _submitted = true);
|
||||
ref.invalidate(_reportDetailProvider(widget.id));
|
||||
ref.read(doctorReportsRefreshSignalProvider.notifier).trigger();
|
||||
ref.read(doctorDashboardRefreshSignalProvider.notifier).trigger();
|
||||
if (mounted) {
|
||||
AppToast.show(context, '审核提交成功', type: AppToastType.success);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../core/app_colors.dart';
|
||||
import '../../core/navigation_provider.dart';
|
||||
import '../../providers/auth_provider.dart';
|
||||
import '../../providers/backoffice_refresh_providers.dart';
|
||||
import '../../utils/backoffice_formatters.dart';
|
||||
import '../../widgets/backoffice_ui.dart';
|
||||
|
||||
@@ -10,6 +11,7 @@ final _reportsProvider = FutureProvider<List<Map<String, dynamic>>>((
|
||||
ref,
|
||||
) async {
|
||||
ref.watch(userSessionIdentityProvider);
|
||||
ref.watch(doctorReportsRefreshSignalProvider);
|
||||
final api = ref.read(apiClientProvider);
|
||||
final res = await api.get('/api/doctor/reports');
|
||||
return (res.data['data'] as List?)?.cast<Map<String, dynamic>>() ?? [];
|
||||
|
||||
@@ -8,6 +8,7 @@ import '../../core/app_module_visuals.dart';
|
||||
import '../../core/app_theme.dart';
|
||||
import '../../core/navigation_provider.dart';
|
||||
import '../../providers/data_providers.dart';
|
||||
import '../../providers/data_refresh_providers.dart';
|
||||
import '../../widgets/app_empty_state.dart';
|
||||
import '../../widgets/app_future_view.dart';
|
||||
import '../../widgets/app_toast.dart';
|
||||
@@ -34,8 +35,7 @@ class _EnterpriseExercisePlanPageState
|
||||
setState(() => _busyItems.add(itemId));
|
||||
try {
|
||||
await ref.read(exerciseServiceProvider).checkIn(itemId);
|
||||
ref.invalidate(currentExercisePlanProvider);
|
||||
ref.invalidate(exercisePlansProvider);
|
||||
ref.read(exerciseDataRefreshSignalProvider.notifier).trigger();
|
||||
} catch (error) {
|
||||
if (mounted) {
|
||||
AppToast.show(
|
||||
@@ -73,8 +73,7 @@ class _EnterpriseExercisePlanPageState
|
||||
setState(() => _deletingPlans.add(id));
|
||||
try {
|
||||
await ref.read(exerciseServiceProvider).deletePlan(id);
|
||||
ref.invalidate(currentExercisePlanProvider);
|
||||
ref.invalidate(exercisePlansProvider);
|
||||
ref.read(exerciseDataRefreshSignalProvider.notifier).trigger();
|
||||
if (mounted) {
|
||||
AppToast.show(context, '运动计划已删除', type: AppToastType.success);
|
||||
}
|
||||
@@ -421,12 +420,12 @@ class _ExercisePlanRow extends StatelessWidget {
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.exerciseLight,
|
||||
color: _phaseColor(phase).withValues(alpha: 0.10),
|
||||
borderRadius: AppRadius.smBorder,
|
||||
),
|
||||
child: Icon(
|
||||
AppModuleVisuals.exercise.icon,
|
||||
color: AppColors.exercise,
|
||||
color: _phaseColor(phase),
|
||||
size: 22,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -170,18 +170,30 @@ class _HomePageState extends ConsumerState<HomePage>
|
||||
children: [
|
||||
_buildHeader(user),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onHorizontalDragStart: _startConversationDrawerDrag,
|
||||
onHorizontalDragUpdate: _updateConversationDrawerDrag,
|
||||
onHorizontalDragEnd: _endConversationDrawerDrag,
|
||||
onHorizontalDragCancel: () => _conversationDragDistance = 0,
|
||||
child: RepaintBoundary(
|
||||
child: _HomeMessages(scrollCtrl: _scrollCtrl),
|
||||
child: ClipRect(
|
||||
child: KeyboardTranslate(
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onHorizontalDragStart: _startConversationDrawerDrag,
|
||||
onHorizontalDragUpdate:
|
||||
_updateConversationDrawerDrag,
|
||||
onHorizontalDragEnd: _endConversationDrawerDrag,
|
||||
onHorizontalDragCancel: () =>
|
||||
_conversationDragDistance = 0,
|
||||
child: RepaintBoundary(
|
||||
child: _HomeMessages(scrollCtrl: _scrollCtrl),
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildBottomBar(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
KeyboardLift(child: _buildBottomBar(context)),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -420,6 +420,8 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
) {
|
||||
final meta = msg.metadata ?? <String, dynamic>{};
|
||||
final backendType = meta['type'] as String? ?? '';
|
||||
final medicationOperation = meta['operation']?.toString() ?? 'create';
|
||||
final isMedicationCheckIn = medicationOperation == 'confirm';
|
||||
final screenWidth = MediaQuery.sizeOf(context).width;
|
||||
|
||||
// 识别是哪种数据类型
|
||||
@@ -449,7 +451,7 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
|
||||
if (isMedication) {
|
||||
// 药品录入 — 主展示区已显示药名+剂量,详情只列额外信息
|
||||
title = '药品录入确认';
|
||||
title = isMedicationCheckIn ? '服药打卡确认' : '药品录入确认';
|
||||
titleIcon = Icons.medication_liquid_outlined;
|
||||
mainLabel = meta['name'] as String? ?? '';
|
||||
mainValue = meta['dosage'] as String? ?? '';
|
||||
@@ -834,7 +836,9 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
msg.isReadOnly ? '历史记录' : '录入成功',
|
||||
msg.isReadOnly
|
||||
? '历史记录'
|
||||
: (isMedicationCheckIn ? '打卡成功' : '录入成功'),
|
||||
style: const TextStyle(
|
||||
fontSize: 19,
|
||||
fontWeight: FontWeight.w700,
|
||||
@@ -882,9 +886,9 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
const Text(
|
||||
'确认录入',
|
||||
style: TextStyle(
|
||||
Text(
|
||||
isMedicationCheckIn ? '确认打卡' : '确认录入',
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
@@ -1005,7 +1009,7 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: isUser ? const Color(0xFF5F56FF) : const Color(0xFFF8F8FF),
|
||||
color: isUser ? const Color(0xFF5F56FF) : const Color(0xFFFAFAFF),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(isUser ? 18.6 : 3),
|
||||
topRight: Radius.circular(isUser ? 3 : 18.6),
|
||||
|
||||
@@ -10,6 +10,7 @@ import '../../core/app_theme.dart';
|
||||
import '../../core/navigation_provider.dart';
|
||||
import '../../providers/auth_provider.dart';
|
||||
import '../../providers/data_providers.dart';
|
||||
import '../../providers/data_refresh_providers.dart';
|
||||
import '../../widgets/app_empty_state.dart';
|
||||
import '../../widgets/app_error_state.dart';
|
||||
import '../../widgets/app_toast.dart';
|
||||
@@ -62,8 +63,7 @@ class _MedicationCheckInPageState extends ConsumerState<MedicationCheckInPage> {
|
||||
throw const ApiException('该次服药已经打卡');
|
||||
}
|
||||
}
|
||||
ref.invalidate(medicationReminderProvider);
|
||||
ref.invalidate(medicationListProvider);
|
||||
ref.read(medicationDataRefreshSignalProvider.notifier).trigger();
|
||||
_load();
|
||||
} catch (error) {
|
||||
if (mounted) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import '../../core/app_design_tokens.dart';
|
||||
import '../../core/app_theme.dart';
|
||||
import '../../core/navigation_provider.dart';
|
||||
import '../../providers/data_providers.dart';
|
||||
import '../../providers/data_refresh_providers.dart';
|
||||
import '../../widgets/app_error_state.dart';
|
||||
import '../../widgets/app_toast.dart';
|
||||
import '../../widgets/common_widgets.dart';
|
||||
@@ -117,8 +118,7 @@ class _MedicationEditPageState extends ConsumerState<MedicationEditPage> {
|
||||
} else {
|
||||
await service.create(payload);
|
||||
}
|
||||
ref.invalidate(medicationListProvider);
|
||||
ref.invalidate(medicationReminderProvider);
|
||||
ref.read(medicationDataRefreshSignalProvider.notifier).trigger();
|
||||
if (mounted) {
|
||||
AppToast.show(
|
||||
context,
|
||||
@@ -164,8 +164,7 @@ class _MedicationEditPageState extends ConsumerState<MedicationEditPage> {
|
||||
setState(() => _saving = true);
|
||||
try {
|
||||
await ref.read(medicationServiceProvider).deleteMedication(widget.id!);
|
||||
ref.invalidate(medicationListProvider);
|
||||
ref.invalidate(medicationReminderProvider);
|
||||
ref.read(medicationDataRefreshSignalProvider.notifier).trigger();
|
||||
if (mounted) {
|
||||
AppToast.show(context, '用药已删除', type: AppToastType.success);
|
||||
popRoute(ref);
|
||||
|
||||
@@ -9,6 +9,7 @@ import '../../core/app_module_visuals.dart';
|
||||
import '../../core/app_theme.dart';
|
||||
import '../../core/navigation_provider.dart';
|
||||
import '../../providers/data_providers.dart';
|
||||
import '../../providers/data_refresh_providers.dart';
|
||||
import '../../widgets/app_empty_state.dart';
|
||||
import '../../widgets/app_future_view.dart';
|
||||
import '../../widgets/app_toast.dart';
|
||||
@@ -52,8 +53,7 @@ class _MedicationListPageState extends ConsumerState<MedicationListPage> {
|
||||
setState(() => _deleting.add(id));
|
||||
try {
|
||||
await ref.read(medicationServiceProvider).deleteMedication(id);
|
||||
ref.invalidate(medicationListProvider);
|
||||
ref.invalidate(medicationReminderProvider);
|
||||
ref.read(medicationDataRefreshSignalProvider.notifier).trigger();
|
||||
if (mounted) AppToast.show(context, '用药已删除', type: AppToastType.success);
|
||||
} catch (error) {
|
||||
if (mounted) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import '../core/app_theme.dart';
|
||||
import '../core/navigation_provider.dart';
|
||||
import '../providers/auth_provider.dart';
|
||||
import '../providers/data_providers.dart';
|
||||
import '../providers/data_refresh_providers.dart';
|
||||
import '../widgets/common_widgets.dart';
|
||||
import '../widgets/app_empty_state.dart';
|
||||
import '../widgets/app_error_state.dart';
|
||||
@@ -64,7 +65,7 @@ class _DietRecordListPageState extends ConsumerState<DietRecordListPage> {
|
||||
setState(() => _data.removeAt(index));
|
||||
try {
|
||||
await ref.read(dietServiceProvider).deleteRecord(id);
|
||||
ref.invalidate(dietRecordsProvider);
|
||||
ref.read(dietDataRefreshSignalProvider.notifier).trigger();
|
||||
if (mounted) {
|
||||
AppToast.show(context, '已删除', type: AppToastType.success);
|
||||
}
|
||||
@@ -100,6 +101,9 @@ class _DietRecordListPageState extends ConsumerState<DietRecordListPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ref.listen(dietDataRefreshSignalProvider, (previous, next) {
|
||||
if (previous != null && previous != next) _refresh();
|
||||
});
|
||||
if (_loading) {
|
||||
return GradientScaffold(
|
||||
appBar: AppBar(
|
||||
@@ -451,7 +455,9 @@ class _DietRecordListPageState extends ConsumerState<DietRecordListPage> {
|
||||
});
|
||||
if (!ctx.mounted) return;
|
||||
Navigator.pop(ctx);
|
||||
await _refresh();
|
||||
ref
|
||||
.read(dietDataRefreshSignalProvider.notifier)
|
||||
.trigger();
|
||||
} catch (_) {
|
||||
if (!ctx.mounted) return;
|
||||
setDialogState(() => saving = false);
|
||||
@@ -1047,7 +1053,7 @@ class _ExercisePlanPageState extends ConsumerState<ExercisePlanPage> {
|
||||
setState(() => _busyItems.add(itemId));
|
||||
try {
|
||||
await ref.read(exerciseServiceProvider).checkIn(itemId);
|
||||
ref.invalidate(currentExercisePlanProvider);
|
||||
ref.read(exerciseDataRefreshSignalProvider.notifier).trigger();
|
||||
_load();
|
||||
} catch (e) {
|
||||
debugPrint('[ExercisePlan] 打卡失败: $e');
|
||||
@@ -1061,7 +1067,7 @@ class _ExercisePlanPageState extends ConsumerState<ExercisePlanPage> {
|
||||
|
||||
Future<void> _deletePlan(String id) async {
|
||||
await ref.read(exerciseServiceProvider).deletePlan(id);
|
||||
ref.invalidate(currentExercisePlanProvider);
|
||||
ref.read(exerciseDataRefreshSignalProvider.notifier).trigger();
|
||||
_load();
|
||||
}
|
||||
|
||||
@@ -1657,7 +1663,7 @@ class _ExercisePlanDetailPageState
|
||||
setState(() => _busyItems.add(itemId));
|
||||
try {
|
||||
await ref.read(exerciseServiceProvider).checkIn(itemId);
|
||||
ref.invalidate(currentExercisePlanProvider);
|
||||
ref.read(exerciseDataRefreshSignalProvider.notifier).trigger();
|
||||
_load();
|
||||
} catch (e) {
|
||||
debugPrint('[ExercisePlanDetail] 打卡失败: $e');
|
||||
@@ -2182,7 +2188,7 @@ class _ExercisePlanCreatePageState
|
||||
'reminderTime':
|
||||
'${_reminderTime.hour.toString().padLeft(2, '0')}:${_reminderTime.minute.toString().padLeft(2, '0')}',
|
||||
});
|
||||
ref.invalidate(currentExercisePlanProvider);
|
||||
ref.read(exerciseDataRefreshSignalProvider.notifier).trigger();
|
||||
if (mounted) {
|
||||
popRoute(ref);
|
||||
}
|
||||
@@ -3805,8 +3811,19 @@ class _HealthCalendarPageState extends ConsumerState<HealthCalendarPage> {
|
||||
_loadMonth();
|
||||
}
|
||||
|
||||
void _refreshScheduleData() {
|
||||
_dayCache.clear();
|
||||
_loadMonth();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ref.listen(exerciseDataRefreshSignalProvider, (previous, next) {
|
||||
if (previous != null && previous != next) _refreshScheduleData();
|
||||
});
|
||||
ref.listen(medicationDataRefreshSignalProvider, (previous, next) {
|
||||
if (previous != null && previous != next) _refreshScheduleData();
|
||||
});
|
||||
return GradientScaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
|
||||
@@ -12,3 +12,9 @@ final adminDoctorsRefreshSignalProvider =
|
||||
|
||||
final doctorFollowupsRefreshSignalProvider =
|
||||
NotifierProvider<BackofficeRefreshSignal, int>(BackofficeRefreshSignal.new);
|
||||
|
||||
final doctorReportsRefreshSignalProvider =
|
||||
NotifierProvider<BackofficeRefreshSignal, int>(BackofficeRefreshSignal.new);
|
||||
|
||||
final doctorDashboardRefreshSignalProvider =
|
||||
NotifierProvider<BackofficeRefreshSignal, int>(BackofficeRefreshSignal.new);
|
||||
|
||||
@@ -5,10 +5,27 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'auth_provider.dart';
|
||||
import 'conversation_history_provider.dart';
|
||||
import 'data_providers.dart';
|
||||
import 'data_refresh_providers.dart';
|
||||
import '../utils/sse_handler.dart';
|
||||
|
||||
enum MessageType { text, dataConfirm, agentWelcome, taskCard }
|
||||
|
||||
List<ChatMessage> prependTodayHealthCard(
|
||||
List<ChatMessage> messages, {
|
||||
DateTime? now,
|
||||
}) {
|
||||
return [
|
||||
ChatMessage(
|
||||
id: 'task_card',
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
createdAt: now ?? DateTime.now(),
|
||||
type: MessageType.taskCard,
|
||||
),
|
||||
...messages,
|
||||
];
|
||||
}
|
||||
|
||||
class ChatMessage {
|
||||
final String id;
|
||||
final String role;
|
||||
@@ -133,10 +150,9 @@ class ChatNotifier extends Notifier<ChatState> {
|
||||
|
||||
msgs[i].confirmed = true;
|
||||
state = state.copyWith(messages: msgs);
|
||||
ref.invalidate(medicationListProvider);
|
||||
ref.invalidate(medicationReminderProvider);
|
||||
ref.read(medicationDataRefreshSignalProvider.notifier).trigger();
|
||||
ref.invalidate(latestHealthProvider);
|
||||
ref.invalidate(currentExercisePlanProvider);
|
||||
ref.read(exerciseDataRefreshSignalProvider.notifier).trigger();
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -161,18 +177,7 @@ class ChatNotifier extends Notifier<ChatState> {
|
||||
|
||||
void insertTaskCard() {
|
||||
if (state.messages.any((m) => m.type == MessageType.taskCard)) return;
|
||||
state = state.copyWith(
|
||||
messages: [
|
||||
ChatMessage(
|
||||
id: 'task_card',
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
createdAt: DateTime.now(),
|
||||
type: MessageType.taskCard,
|
||||
),
|
||||
...state.messages,
|
||||
],
|
||||
);
|
||||
state = state.copyWith(messages: prependTodayHealthCard(state.messages));
|
||||
}
|
||||
|
||||
Future<String?> loadConversation(String convId) async {
|
||||
@@ -212,7 +217,7 @@ class ChatNotifier extends Notifier<ChatState> {
|
||||
}).toList();
|
||||
|
||||
state = ChatState(
|
||||
messages: messages,
|
||||
messages: prependTodayHealthCard(messages),
|
||||
conversationId: convId,
|
||||
activeAgent: ActiveAgent.default_,
|
||||
isViewingHistory: true,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'auth_provider.dart';
|
||||
import 'data_refresh_providers.dart';
|
||||
import '../services/health_service.dart';
|
||||
import '../services/admin_service.dart';
|
||||
import '../services/in_app_notification_service.dart';
|
||||
@@ -63,6 +64,7 @@ final medicationListProvider = FutureProvider<List<Map<String, dynamic>>>((
|
||||
ref,
|
||||
) async {
|
||||
ref.watch(userSessionIdentityProvider);
|
||||
ref.watch(medicationDataRefreshSignalProvider);
|
||||
final service = ref.watch(medicationServiceProvider);
|
||||
return service.getList();
|
||||
});
|
||||
@@ -71,6 +73,7 @@ final exercisePlansProvider = FutureProvider<List<Map<String, dynamic>>>((
|
||||
ref,
|
||||
) async {
|
||||
ref.watch(userSessionIdentityProvider);
|
||||
ref.watch(exerciseDataRefreshSignalProvider);
|
||||
return ref.watch(exerciseServiceProvider).getPlans();
|
||||
});
|
||||
|
||||
@@ -85,6 +88,7 @@ final dietRecordsProvider = FutureProvider<List<Map<String, dynamic>>>((
|
||||
ref,
|
||||
) async {
|
||||
ref.watch(userSessionIdentityProvider);
|
||||
ref.watch(dietDataRefreshSignalProvider);
|
||||
return ref.watch(dietServiceProvider).getRecords();
|
||||
});
|
||||
|
||||
@@ -92,6 +96,7 @@ final medicationReminderProvider = FutureProvider<List<Map<String, dynamic>>>((
|
||||
ref,
|
||||
) async {
|
||||
ref.watch(userSessionIdentityProvider);
|
||||
ref.watch(medicationDataRefreshSignalProvider);
|
||||
final service = ref.watch(medicationServiceProvider);
|
||||
return service.getReminders();
|
||||
});
|
||||
@@ -109,6 +114,7 @@ final currentExercisePlanProvider = FutureProvider<Map<String, dynamic>?>((
|
||||
ref,
|
||||
) async {
|
||||
ref.watch(userSessionIdentityProvider);
|
||||
ref.watch(exerciseDataRefreshSignalProvider);
|
||||
final service = ref.watch(exerciseServiceProvider);
|
||||
return service.getCurrentPlan().timeout(const Duration(seconds: 8));
|
||||
});
|
||||
|
||||
18
health_app/lib/providers/data_refresh_providers.dart
Normal file
18
health_app/lib/providers/data_refresh_providers.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class DataRefreshSignal extends Notifier<int> {
|
||||
@override
|
||||
int build() => 0;
|
||||
|
||||
void trigger() => state++;
|
||||
}
|
||||
|
||||
final exerciseDataRefreshSignalProvider =
|
||||
NotifierProvider<DataRefreshSignal, int>(DataRefreshSignal.new);
|
||||
|
||||
final medicationDataRefreshSignalProvider =
|
||||
NotifierProvider<DataRefreshSignal, int>(DataRefreshSignal.new);
|
||||
|
||||
final dietDataRefreshSignalProvider = NotifierProvider<DataRefreshSignal, int>(
|
||||
DataRefreshSignal.new,
|
||||
);
|
||||
@@ -15,3 +15,20 @@ class KeyboardLift extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 只移动已有前景图层,不在键盘动画期间重新布局子组件。
|
||||
class KeyboardTranslate extends StatelessWidget {
|
||||
final Widget child;
|
||||
|
||||
const KeyboardTranslate({super.key, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bottom = MediaQuery.viewInsetsOf(context).bottom;
|
||||
return Transform.translate(
|
||||
offset: Offset(0, -bottom),
|
||||
transformHitTests: true,
|
||||
child: RepaintBoundary(child: child),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user