feat: 二级页面色彩刷新 + 用药/通知/设备重构 + 后端健康档案/通知管线增强 + 大量测试
## 后端 - 健康档案: 新增手术状态字段 + EF 迁移; HealthArchiveService 新增查询方法 - 健康记录: HealthRecordService 新增批量/统计方法; 契约扩展 - 用药: 新增 MedicationScheduleStatus 枚举; MedicationService 排班逻辑调整 - 通知: EfUserNotificationPipeline 重构; 新增 EfReminderCatchUpService; 通知管线支持更多场景 - 用户: UserService 账号删除逻辑; 新增 local_account_file_cleanup; EfUserRepository 扩展 - AI: medication_agent_handler 微调; prompt_manager 优化; AiConversationService 上下文处理 - Endpoint: doctor/medication/exercise/health/notification/user 等多接口调整 - BackgroundService: health_record_reminder_service 重构, 提醒补漏逻辑 - 测试: 新增 account_deletion/doctor_endpoint/medication_schedule/medication_update/prompt_manager 测试 ## 前端 - UI 系统: app_theme 大幅重构; app_colors/app_design_tokens/app_module_visuals 调整; 二级页面色彩刷新 - 主页: home_page 背景渐变 + 消息列表提取 _HomeMessages + 通知检查逻辑; chat_messages_view 全面重构 - 用药: medication_list/edit/checkin 三页重构, 新增 medication_ui_logic 抽取 - 通知: notification_prefs_page 重构, 新增 notification_prefs_logic; notification_center 优化 - 设备: device_management 重构, 新增 device_sync_ui_logic; device_scan 优化 - 趋势图: trend_page 大幅重构 - 登录: login_page 重构 - 个人资料: 新增 profile_edit_page; profile_page 优化 - 运动: 新增 exercise/ 目录 + care_plan_ui_logic - 其他: remaining_pages/report_pages/health_drawer/admin/doctor 等多页面调整 - 组件: common_widgets/app_empty_state/app_error_state/app_future_view/app_toast/ai_content 优化 - Provider: chat_provider/consultation_provider/data_providers/auth_provider 调整 - AndroidManifest: 移除多余权限 - 测试: 新增 ai_content/care_plan/home_message/login_flow/medication_checkin/medication_ui/notification_prefs/profile_device/secondary_page/swipe_delete 等大量测试 ## 文档 - 新增 ui-design-system.md 设计系统文档 - 新增 secondary-page-color-refresh 计划 + specs 目录
This commit is contained in:
212
health_app/test/secondary_page_visuals_test.dart
Normal file
212
health_app/test/secondary_page_visuals_test.dart
Normal file
@@ -0,0 +1,212 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:health_app/core/app_colors.dart';
|
||||
import 'package:health_app/core/app_design_tokens.dart';
|
||||
import 'package:health_app/core/app_theme.dart';
|
||||
import 'package:health_app/widgets/common_widgets.dart';
|
||||
import 'package:health_app/widgets/app_empty_state.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('secondary page separates neutral canvas from white app bar', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: AppTheme.lightTheme,
|
||||
home: GradientScaffold(
|
||||
appBar: AppBar(title: const Text('用药管理')),
|
||||
body: const SizedBox.expand(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final scaffold = tester.widget<Scaffold>(find.byType(Scaffold));
|
||||
expect(scaffold.backgroundColor, AppColors.background);
|
||||
expect(AppTheme.lightTheme.appBarTheme.backgroundColor, Colors.white);
|
||||
});
|
||||
|
||||
testWidgets('create fab uses one white gradient-outline visual', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
floatingActionButton: AppCreateFab(tooltip: '添加用药', onPressed: () {}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final border = tester.widget<DecoratedBox>(
|
||||
find.byKey(const ValueKey('app-create-fab-border')),
|
||||
);
|
||||
final decoration = border.decoration as BoxDecoration;
|
||||
expect(decoration.gradient, AppColors.actionOutlineGradient);
|
||||
expect(decoration.borderRadius, AppRadius.pillBorder);
|
||||
expect(find.byIcon(Icons.add_rounded), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('empty state uses a flat restrained icon surface', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: AppEmptyState(
|
||||
icon: Icons.event_available_outlined,
|
||||
title: '今日没有安排',
|
||||
iconColor: AppColors.success,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final surface = tester.widget<Container>(
|
||||
find.byKey(const ValueKey('empty-state-icon-surface')),
|
||||
);
|
||||
final decoration = surface.decoration as BoxDecoration;
|
||||
expect(decoration.gradient, isNull);
|
||||
expect(decoration.boxShadow, isNull);
|
||||
});
|
||||
|
||||
test('health metrics and notification preferences use lucide icons', () {
|
||||
final trend = File('lib/pages/chart/trend_page.dart').readAsStringSync();
|
||||
final notifications = File(
|
||||
'lib/pages/settings/notification_prefs_page.dart',
|
||||
).readAsStringSync();
|
||||
|
||||
expect(trend, contains('LucideIcons.gauge'));
|
||||
expect(trend, contains('LucideIcons.heartPulse'));
|
||||
expect(notifications, contains('AppModuleVisuals.medication.icon'));
|
||||
expect(notifications, contains('LucideIcons.slidersHorizontal'));
|
||||
});
|
||||
|
||||
test(
|
||||
'login and registration keep the image background with neutral forms',
|
||||
() {
|
||||
final login = File('lib/pages/auth/login_page.dart').readAsStringSync();
|
||||
|
||||
expect(login, contains("Image.asset(\n _loginBg"));
|
||||
expect(login, contains('Colors.white.withValues(alpha: 0.88)'));
|
||||
expect(login, contains('fillColor: AppColors.cardInner'));
|
||||
expect(login, contains('showDragHandle: true'));
|
||||
},
|
||||
);
|
||||
|
||||
test('today health special states do not use a filled warning row', () {
|
||||
final homeCard = File(
|
||||
'lib/pages/home/widgets/chat_messages_view.dart',
|
||||
).readAsStringSync();
|
||||
|
||||
expect(homeCard, contains("'overdue': LucideIcons.circleAlert"));
|
||||
expect(homeCard, contains('color: Colors.transparent'));
|
||||
expect(homeCard, isNot(contains('isOverdue ? const Color(0xFFFFEBEE)')));
|
||||
expect(homeCard, contains('EdgeInsets.fromLTRB(14, 5, 14, 7)'));
|
||||
expect(homeCard, contains('EdgeInsets.symmetric(vertical: 1)'));
|
||||
});
|
||||
|
||||
test('active create and report states follow the shared visual system', () {
|
||||
final remaining = File('lib/pages/remaining_pages.dart').readAsStringSync();
|
||||
final reports = File(
|
||||
'lib/pages/report/report_pages.dart',
|
||||
).readAsStringSync();
|
||||
|
||||
expect(remaining, contains("AppGradientOutlineButton(label: '保存计划'"));
|
||||
expect(reports, contains('icon: LucideIcons.camera'));
|
||||
expect(reports, contains('icon: LucideIcons.images'));
|
||||
final pdfState = reports.substring(
|
||||
reports.indexOf('Widget _buildPdf'),
|
||||
reports.indexOf('String _absoluteUrl'),
|
||||
);
|
||||
expect(pdfState, contains('return AppEmptyState('));
|
||||
expect(pdfState, contains('icon: AppModuleVisuals.report.icon'));
|
||||
});
|
||||
|
||||
test('secondary module colors do not keep the old mixed palettes', () {
|
||||
final diet = File(
|
||||
'lib/pages/diet/diet_nutrition_widgets.dart',
|
||||
).readAsStringSync();
|
||||
final remaining = File('lib/pages/remaining_pages.dart').readAsStringSync();
|
||||
final reports = File(
|
||||
'lib/pages/report/report_pages.dart',
|
||||
).readAsStringSync();
|
||||
final medication = File(
|
||||
'lib/pages/medication/medication_list_page.dart',
|
||||
).readAsStringSync();
|
||||
final checkin = File(
|
||||
'lib/pages/medication/medication_checkin_page.dart',
|
||||
).readAsStringSync();
|
||||
final exercise = File(
|
||||
'lib/pages/exercise/exercise_plan_page.dart',
|
||||
).readAsStringSync();
|
||||
|
||||
expect(diet, contains('static const primary = Color(0xFF6476E8)'));
|
||||
expect(remaining, isNot(contains('AppModuleVisuals.diet.color')));
|
||||
expect(reports, contains('const _reportPageSoft = Color(0xFFF0F0FF)'));
|
||||
expect(reports, isNot(contains('Color(0xFF60A5FA)')));
|
||||
expect(medication, contains('AppColors.blueMeasure'));
|
||||
expect(checkin, contains('AppColors.blueMeasure'));
|
||||
expect(exercise, contains('AppColors.blueMeasure'));
|
||||
});
|
||||
|
||||
test('trend colors and status text colors stay visually distinct', () {
|
||||
final colors = File('lib/core/app_colors.dart').readAsStringSync();
|
||||
final trend = File('lib/pages/chart/trend_page.dart').readAsStringSync();
|
||||
|
||||
expect(trend, contains('class _TrendColors'));
|
||||
expect(trend, contains("'color': _TrendColors.bloodPressure"));
|
||||
expect(trend, contains("'color': _TrendColors.heartRate"));
|
||||
expect(trend, isNot(contains("'color': Color(0xFFEF4444)")));
|
||||
expect(trend, isNot(contains("'color': Color(0xFFF59E0B)")));
|
||||
expect(colors, contains('successText = Color(0xFF15803D)'));
|
||||
expect(colors, contains('errorText = Color(0xFFDC2626)'));
|
||||
expect(colors, contains('warningText = Color(0xFFB45309)'));
|
||||
});
|
||||
|
||||
test('exercise rows use the shared leading icon list pattern', () {
|
||||
final exercise = File(
|
||||
'lib/pages/exercise/exercise_plan_page.dart',
|
||||
).readAsStringSync();
|
||||
final row = exercise.substring(
|
||||
exercise.indexOf('class _ExercisePlanRow'),
|
||||
exercise.indexOf('class _CompactActionButton'),
|
||||
);
|
||||
|
||||
expect(row, contains('AppModuleVisuals.exercise.icon'));
|
||||
expect(row, contains('color: AppColors.exerciseLight'));
|
||||
expect(row, contains('left: 68'));
|
||||
expect(row, isNot(contains('width: 16')));
|
||||
final modules = File('lib/core/app_module_visuals.dart').readAsStringSync();
|
||||
expect(modules, contains('icon: Icons.directions_run_outlined'));
|
||||
});
|
||||
|
||||
test(
|
||||
'report utility action and notification rows stay neutral and compact',
|
||||
() {
|
||||
final analysis = File(
|
||||
'lib/pages/report/ai_analysis_page.dart',
|
||||
).readAsStringSync();
|
||||
final notifications = File(
|
||||
'lib/pages/notifications/notification_center_page.dart',
|
||||
).readAsStringSync();
|
||||
|
||||
final originalReportAction = analysis.substring(
|
||||
analysis.indexOf("label: const Text('查看原始报告')"),
|
||||
analysis.indexOf("label: const Text('查看原始报告')") + 500,
|
||||
);
|
||||
expect(
|
||||
originalReportAction,
|
||||
contains('foregroundColor: AppColors.textPrimary'),
|
||||
);
|
||||
expect(originalReportAction, contains('color: AppColors.border'));
|
||||
expect(notifications, contains('BoxConstraints(minHeight: 82)'));
|
||||
expect(notifications, contains('width: 68'));
|
||||
expect(notifications, contains("const TextSpan(text: ' 条未读消息')"));
|
||||
expect(
|
||||
notifications,
|
||||
isNot(contains('color: AppColors.notificationLight')),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user