feat: 健康仪表盘配色调整 + 登录页品牌升级 + iOS 配置 + 隐私文案修订 + 文档清理
## UI 配色 - 健康仪表盘: 背景从蓝紫粉三色渐变改为 #4FACFE 纯色蓝, 白字清晰不抢眼 - app_colors / app_design_tokens / app_theme: 配色体系微调 - 多个 widget 和页面跟随配色更新(health_drawer/admin_drawer/doctor_drawer/enterprise_widgets 等) ## 登录页品牌升级 - 新增品牌素材: drawer_background_v2 / login_background_v2 / health_login_character_transparent - login_page 重构, 接入新品牌视觉 - app.dart 启动流程调整 ## iOS / Android 配置 - Info.plist: 权限描述调整 - AppIcon / LaunchImage: 资源更新 - AndroidManifest: 权限微调 ## 隐私文案修订 - privacy.html: 蓝牙设备描述调整为"标准蓝牙血压服务", 移除未上线设备类型表述 - terms.html: 移除"在线医生咨询"相关条款, 服务范围收窄 ## 通知中心 - notification_center_page: 重构通知项布局 ## 其他 - 删除已完成的计划/spec 文档(ui-system-first-pass / apple-sign-in / secondary-page-color-refresh / notification-preferences-design / ui-design-system) - 新增 native_navigation_test - 新增 HANDOFF 交接文档 - home_message_order_test 更新 - .gitignore 调整
This commit is contained in:
@@ -19,7 +19,7 @@ class LoginPage extends ConsumerStatefulWidget {
|
||||
}
|
||||
|
||||
class _LoginPageState extends ConsumerState<LoginPage> {
|
||||
static const _loginBg = 'assets/branding/login_background_v1.png';
|
||||
static const _loginBg = 'assets/branding/login_background_v2.png';
|
||||
static const _loginActionGradient = AppColors.primaryGradient;
|
||||
|
||||
void _openStaticText(String type) {
|
||||
@@ -220,7 +220,7 @@ class _LoginPageState extends ConsumerState<LoginPage> {
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 21,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
letterSpacing: 0,
|
||||
),
|
||||
@@ -303,7 +303,7 @@ class _LoginPageState extends ConsumerState<LoginPage> {
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -350,7 +350,7 @@ class _LoginPageState extends ConsumerState<LoginPage> {
|
||||
'选择健康服务医生',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
@@ -438,10 +438,12 @@ class _LoginPageState extends ConsumerState<LoginPage> {
|
||||
body: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Image.asset(
|
||||
_loginBg,
|
||||
fit: BoxFit.cover,
|
||||
alignment: Alignment.topCenter,
|
||||
child: RepaintBoundary(
|
||||
child: Image.asset(
|
||||
_loginBg,
|
||||
fit: BoxFit.cover,
|
||||
alignment: Alignment.topCenter,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
@@ -460,16 +462,13 @@ class _LoginPageState extends ConsumerState<LoginPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedPadding(
|
||||
duration: const Duration(milliseconds: 180),
|
||||
curve: Curves.easeOutCubic,
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.viewInsetsOf(context).bottom,
|
||||
),
|
||||
_LoginKeyboardLift(
|
||||
child: SafeArea(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return SingleChildScrollView(
|
||||
keyboardDismissBehavior:
|
||||
ScrollViewKeyboardDismissBehavior.onDrag,
|
||||
padding: const EdgeInsets.fromLTRB(20, 22, 20, 24),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
@@ -589,7 +588,7 @@ class _LoginPageState extends ConsumerState<LoginPage> {
|
||||
_isLogin ? '没有账号?去注册' : '已有账号?去登录',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -613,6 +612,23 @@ class _LoginPageState extends ConsumerState<LoginPage> {
|
||||
}
|
||||
}
|
||||
|
||||
class _LoginKeyboardLift extends StatelessWidget {
|
||||
final Widget child;
|
||||
|
||||
const _LoginKeyboardLift({required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final keyboardHeight = MediaQuery.viewInsetsOf(context).bottom;
|
||||
final lift = (keyboardHeight * 0.42).clamp(0.0, 150.0);
|
||||
return Transform.translate(
|
||||
offset: Offset(0, -lift),
|
||||
transformHitTests: true,
|
||||
child: RepaintBoundary(child: child),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LoginHero extends StatelessWidget {
|
||||
const _LoginHero();
|
||||
|
||||
@@ -626,7 +642,7 @@ class _LoginHero extends StatelessWidget {
|
||||
'小脉健康',
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
letterSpacing: 0,
|
||||
),
|
||||
@@ -676,26 +692,11 @@ class _BrandMark extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: 108,
|
||||
height: 108,
|
||||
child: Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.72),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white.withValues(alpha: 0.86)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColors.primary.withValues(alpha: 0.10),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Image.asset(
|
||||
'assets/branding/health_icon_master.png',
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
width: 116,
|
||||
height: 116,
|
||||
child: Image.asset(
|
||||
'assets/branding/health_login_character_transparent.png',
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -981,7 +982,7 @@ class _PrimaryButton extends StatelessWidget {
|
||||
style: const TextStyle(
|
||||
fontSize: 17,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -555,7 +555,7 @@ class _TrendPageState extends ConsumerState<TrendPage> {
|
||||
_displayValue(record),
|
||||
style: const TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
height: 1.05,
|
||||
),
|
||||
|
||||
@@ -618,7 +618,7 @@ class _DevicesHeader extends StatelessWidget {
|
||||
'已绑定设备',
|
||||
style: TextStyle(
|
||||
fontSize: 19,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
@@ -627,7 +627,7 @@ class _DevicesHeader extends StatelessWidget {
|
||||
'$deviceCount 台设备',
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
@@ -685,7 +685,7 @@ class _DeviceSection extends StatelessWidget {
|
||||
type.label,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
@@ -783,7 +783,7 @@ class _BoundDeviceTile extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
@@ -822,7 +822,7 @@ class _BoundDeviceTile extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
height: 1,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.successText,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -399,7 +399,7 @@ class _ScanningEmpty extends StatelessWidget {
|
||||
scanning ? '正在搜索设备' : '暂未发现设备',
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
@@ -479,7 +479,7 @@ class _DeviceResultTile extends StatelessWidget {
|
||||
type?.label ?? '健康设备',
|
||||
style: const TextStyle(
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
@@ -516,7 +516,7 @@ class _DeviceResultTile extends StatelessWidget {
|
||||
),
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
child: Text(connecting ? '连接中' : '连接'),
|
||||
|
||||
@@ -539,7 +539,7 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
|
||||
'识别结果',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
@@ -750,7 +750,7 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
|
||||
'AI饮食建议',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -42,7 +42,7 @@ class DietCalorieRing extends StatelessWidget {
|
||||
'$calories',
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -341,7 +341,7 @@ class _ExercisePlanGroup extends StatelessWidget {
|
||||
'${_phaseLabel(phase)}(${plans.length})',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -42,7 +42,7 @@ class ConversationHistoryPage extends ConsumerWidget {
|
||||
'清空',
|
||||
style: TextStyle(
|
||||
color: AppColors.errorText,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -201,7 +201,7 @@ class _HistoryTile extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:io';
|
||||
import 'dart:async';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
@@ -31,6 +30,26 @@ class _HomePageState extends ConsumerState<HomePage>
|
||||
final _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
String? _pickedImagePath;
|
||||
Timer? _notificationTimer;
|
||||
double _conversationDragDistance = 0;
|
||||
|
||||
void _startConversationDrawerDrag(DragStartDetails details) {
|
||||
_conversationDragDistance = 0;
|
||||
}
|
||||
|
||||
void _updateConversationDrawerDrag(DragUpdateDetails details) {
|
||||
_conversationDragDistance += details.delta.dx;
|
||||
if (_conversationDragDistance < 0) {
|
||||
_conversationDragDistance = 0;
|
||||
}
|
||||
if (_conversationDragDistance >= 42) {
|
||||
_conversationDragDistance = 0;
|
||||
_scaffoldKey.currentState?.openDrawer();
|
||||
}
|
||||
}
|
||||
|
||||
void _endConversationDrawerDrag(DragEndDetails details) {
|
||||
_conversationDragDistance = 0;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -134,9 +153,7 @@ class _HomePageState extends ConsumerState<HomePage>
|
||||
key: _scaffoldKey,
|
||||
backgroundColor: const Color(0xFFFFFCFF),
|
||||
drawer: const HealthDrawer(),
|
||||
drawerEnableOpenDragGesture: true,
|
||||
drawerDragStartBehavior: DragStartBehavior.down,
|
||||
drawerEdgeDragWidth: MediaQuery.sizeOf(context).width * 0.8,
|
||||
drawerEnableOpenDragGesture: false,
|
||||
body: DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
@@ -150,8 +167,15 @@ class _HomePageState extends ConsumerState<HomePage>
|
||||
children: [
|
||||
_buildHeader(user),
|
||||
Expanded(
|
||||
child: RepaintBoundary(
|
||||
child: _HomeMessages(scrollCtrl: _scrollCtrl),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onHorizontalDragStart: _startConversationDrawerDrag,
|
||||
onHorizontalDragUpdate: _updateConversationDrawerDrag,
|
||||
onHorizontalDragEnd: _endConversationDrawerDrag,
|
||||
onHorizontalDragCancel: () => _conversationDragDistance = 0,
|
||||
child: RepaintBoundary(
|
||||
child: _HomeMessages(scrollCtrl: _scrollCtrl),
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildBottomBar(context),
|
||||
@@ -197,7 +221,7 @@ class _HomePageState extends ConsumerState<HomePage>
|
||||
'${_getGreeting()},$name',
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
@@ -657,7 +681,7 @@ class _HeaderIconButton extends StatelessWidget {
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -231,7 +231,7 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
info.$2,
|
||||
style: AppTextStyles.chatTitle.copyWith(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -692,7 +692,7 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
text: mainValue,
|
||||
style: TextStyle(
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: abnormal
|
||||
? AppColors.error
|
||||
: AppColors.textPrimary,
|
||||
@@ -886,7 +886,7 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
'确认录入',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
@@ -1416,7 +1416,7 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
'今日健康',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
@@ -1570,8 +1570,7 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
'健康指标',
|
||||
trailing: trailing,
|
||||
status: 'warning',
|
||||
iconColor: AppColors.warning,
|
||||
iconBg: AppColors.warningLight,
|
||||
iconGradient: AppModuleVisuals.health.gradient,
|
||||
onTap: () => pushRoute(ref, 'trend'),
|
||||
),
|
||||
);
|
||||
@@ -1586,6 +1585,7 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
status: 'done',
|
||||
iconColor: healthIconColor,
|
||||
iconBg: healthIconBg,
|
||||
iconGradient: AppModuleVisuals.health.gradient,
|
||||
onTap: () => pushRoute(ref, 'trend'),
|
||||
),
|
||||
);
|
||||
@@ -1625,6 +1625,7 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
: (now.hour >= 18 ? 'overdue' : 'pending'),
|
||||
iconColor: exIconColor,
|
||||
iconBg: exIconBg,
|
||||
iconGradient: AppModuleVisuals.exercise.gradient,
|
||||
onTap: () => pushRoute(ref, 'exercisePlan'),
|
||||
),
|
||||
);
|
||||
@@ -1642,6 +1643,7 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
status: 'pending',
|
||||
iconColor: exIconColor,
|
||||
iconBg: exIconBg,
|
||||
iconGradient: AppModuleVisuals.exercise.gradient,
|
||||
onTap: () => pushRoute(ref, 'exercisePlan'),
|
||||
),
|
||||
);
|
||||
@@ -1657,6 +1659,7 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
status: 'overdue',
|
||||
iconColor: exIconColor,
|
||||
iconBg: exIconBg,
|
||||
iconGradient: AppModuleVisuals.exercise.gradient,
|
||||
onTap: () => pushRoute(ref, 'exercisePlan'),
|
||||
),
|
||||
);
|
||||
@@ -1672,6 +1675,7 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
status: 'pending',
|
||||
iconColor: exIconColor,
|
||||
iconBg: exIconBg,
|
||||
iconGradient: AppModuleVisuals.exercise.gradient,
|
||||
onTap: () => pushRoute(ref, 'exercisePlan'),
|
||||
),
|
||||
);
|
||||
@@ -1690,12 +1694,13 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
tasks.add(
|
||||
_taskRow(
|
||||
context,
|
||||
LucideIcons.pill,
|
||||
AppModuleVisuals.medication.icon,
|
||||
'用药',
|
||||
trailing: meds.isEmpty ? '暂无用药安排' : '暂时不用吃药',
|
||||
status: 'done',
|
||||
iconColor: medIconColor,
|
||||
iconBg: medIconBg,
|
||||
iconGradient: AppModuleVisuals.medication.gradient,
|
||||
onTap: () => pushRoute(ref, 'medCheckIn'),
|
||||
),
|
||||
);
|
||||
@@ -1748,12 +1753,13 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
tasks.add(
|
||||
_taskRow(
|
||||
context,
|
||||
LucideIcons.pill,
|
||||
AppModuleVisuals.medication.icon,
|
||||
title,
|
||||
trailing: trailing,
|
||||
status: status,
|
||||
iconColor: medIconColor,
|
||||
iconBg: medIconBg,
|
||||
iconGradient: AppModuleVisuals.medication.gradient,
|
||||
onTap: () => pushRoute(ref, 'medCheckIn'),
|
||||
),
|
||||
);
|
||||
@@ -1788,7 +1794,7 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
'今日健康',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
height: 1.15,
|
||||
),
|
||||
@@ -1835,10 +1841,11 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
VoidCallback? onTap,
|
||||
Color? iconColor,
|
||||
Color? iconBg,
|
||||
LinearGradient? iconGradient,
|
||||
}) {
|
||||
final colors = {
|
||||
'done': AppTheme.success,
|
||||
'warning': AppColors.warning,
|
||||
'warning': const Color(0xFFE8562A),
|
||||
'pending': AppColors.textHint,
|
||||
'overdue': AppTheme.error,
|
||||
};
|
||||
@@ -1852,7 +1859,7 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
final ibg = iconBg ?? AppTheme.primaryLight;
|
||||
final textColor = switch (status) {
|
||||
'overdue' => AppColors.error,
|
||||
'warning' => AppColors.warning,
|
||||
'warning' => const Color(0xFFE8562A),
|
||||
_ => AppColors.textPrimary,
|
||||
};
|
||||
return Padding(
|
||||
@@ -1871,10 +1878,15 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: ibg,
|
||||
color: iconGradient == null ? ibg : null,
|
||||
gradient: iconGradient,
|
||||
borderRadius: AppRadius.smBorder,
|
||||
),
|
||||
child: Icon(icon, size: 18, color: ic),
|
||||
child: Icon(
|
||||
icon,
|
||||
size: 18,
|
||||
color: iconGradient == null ? ic : Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
|
||||
@@ -285,7 +285,7 @@ class _MedicationPhaseGroup extends StatelessWidget {
|
||||
'${_phaseLabel(phase)}(${medications.length})',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -196,7 +196,7 @@ class _NotificationCenterPageState
|
||||
'通知中心',
|
||||
style: TextStyle(
|
||||
fontSize: 19,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
@@ -396,7 +396,7 @@ class _UnreadHint extends StatelessWidget {
|
||||
TextSpan(
|
||||
text: '$unreadCount',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.errorText,
|
||||
),
|
||||
),
|
||||
@@ -507,115 +507,118 @@ class _NotificationRow extends StatelessWidget {
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(minHeight: 82),
|
||||
color: Colors.white,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(14, 10, 10, 10),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: visual.lightColor,
|
||||
borderRadius: AppRadius.smBorder,
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(minHeight: showDivider ? 81 : 82),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(14, 10, 10, 10),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: visual.lightColor,
|
||||
borderRadius: AppRadius.smBorder,
|
||||
),
|
||||
child: Icon(visual.icon, size: 21, color: visual.color),
|
||||
),
|
||||
child: Icon(visual.icon, size: 21, color: visual.color),
|
||||
),
|
||||
const SizedBox(width: 11),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: item.isRead
|
||||
? FontWeight.w700
|
||||
: FontWeight.w800,
|
||||
color: AppColors.textPrimary,
|
||||
height: 1.2,
|
||||
const SizedBox(width: 11),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: item.isRead
|
||||
? FontWeight.w600
|
||||
: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: visual.lightColor,
|
||||
borderRadius: AppRadius.pillBorder,
|
||||
),
|
||||
child: Text(
|
||||
visual.label,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: visual.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
item.message,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
height: 1.2,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
SizedBox(
|
||||
width: 68,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
if (item.isRead)
|
||||
const SizedBox(height: 8)
|
||||
else
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: visual.lightColor,
|
||||
borderRadius: AppRadius.pillBorder,
|
||||
),
|
||||
child: Text(
|
||||
visual.label,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: visual.color,
|
||||
),
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.error,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
item.message,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
height: 1.2,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
SizedBox(
|
||||
width: 68,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
if (item.isRead)
|
||||
const SizedBox(height: 8)
|
||||
else
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.error,
|
||||
shape: BoxShape.circle,
|
||||
const SizedBox(height: 7),
|
||||
Text(
|
||||
_formatTime(item.createdAt),
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.right,
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textHint,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 7),
|
||||
Text(
|
||||
_formatTime(item.createdAt),
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.right,
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textHint,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (showDivider)
|
||||
|
||||
@@ -234,7 +234,7 @@ class _FieldLabel extends StatelessWidget {
|
||||
text,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -189,7 +189,7 @@ class _SectionTitle extends StatelessWidget {
|
||||
text,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
@@ -266,7 +266,7 @@ class _ActionRow extends StatelessWidget {
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -147,7 +147,7 @@ class _DietRecordListPageState extends ConsumerState<DietRecordListPage> {
|
||||
: '${_selectedDate.month}月${_selectedDate.day}日饮食',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
@@ -242,7 +242,7 @@ class _DietRecordListPageState extends ConsumerState<DietRecordListPage> {
|
||||
'${d.day}',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: isSel ? Colors.white : AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
@@ -441,7 +441,7 @@ class _DietDaySummary extends StatelessWidget {
|
||||
'${date.month}月${date.day}日',
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
@@ -556,7 +556,7 @@ class _DietTrendPanel extends StatelessWidget {
|
||||
'热量趋势',
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
@@ -803,7 +803,7 @@ class _DietRecordDetailPageState extends ConsumerState<DietRecordDetailPage> {
|
||||
mealNames[d['mealType']?.toString()] ?? '饮食记录',
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
@@ -1222,7 +1222,7 @@ class _ExercisePlanOverviewCard extends StatelessWidget {
|
||||
'$done/$total 天',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: _ExercisePlanPageState._exerciseVisual.color,
|
||||
),
|
||||
),
|
||||
@@ -1375,7 +1375,7 @@ class _ExerciseTodayInlineTask extends StatelessWidget {
|
||||
!hasItem || isRestDay ? '今日无' : (isCompleted ? '撤销' : '打卡'),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -1439,7 +1439,7 @@ class _ExerciseCheckInSummary extends StatelessWidget {
|
||||
'${(progress * 100).round()}%',
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: _ExercisePlanPageState._exerciseVisual.color,
|
||||
),
|
||||
),
|
||||
@@ -1531,7 +1531,7 @@ class _ExerciseSummaryStat extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
@@ -1767,7 +1767,7 @@ class _ExerciseHeroCard extends StatelessWidget {
|
||||
style: const TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
@@ -1896,7 +1896,7 @@ class _TodayExerciseCard extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
@@ -1943,7 +1943,7 @@ class _TodayExerciseCard extends StatelessWidget {
|
||||
isRestDay ? '休息' : (isDone ? '撤销' : '打卡'),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -2062,7 +2062,7 @@ class _ExerciseDayTile extends StatelessWidget {
|
||||
!isToday
|
||||
? '只读'
|
||||
: (isRestDay ? '休息' : (isCompleted ? '撤销' : '打卡')),
|
||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w800),
|
||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -3091,7 +3091,7 @@ class _ArchiveSummary extends StatelessWidget {
|
||||
'$completion%',
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -3166,7 +3166,7 @@ class _Section extends StatelessWidget {
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
@@ -4375,7 +4375,7 @@ class StaticTextPage extends ConsumerWidget {
|
||||
|
||||
### 一、我们收集的信息
|
||||
|
||||
为向您提供健康管理、AI 健康咨询、报告解读、饮食识别、用药管理、运动计划、在线医生咨询和蓝牙设备同步等功能,我们会根据您使用的具体功能收集以下信息:
|
||||
为向您提供健康管理、AI 健康咨询、报告解读、饮食识别、用药管理、运动计划和蓝牙设备同步等功能,我们会根据您使用的具体功能收集以下信息:
|
||||
|
||||
1. 账号与身份信息
|
||||
- 手机号码:用于注册、登录、身份识别和账号安全验证。
|
||||
@@ -4397,7 +4397,7 @@ class StaticTextPage extends ConsumerWidget {
|
||||
- 未经您主动选择或确认,我们不会读取您的相册其他内容,也不会持续访问您的相机。
|
||||
|
||||
4. 蓝牙设备信息
|
||||
- 当您使用蓝牙设备功能时,我们会扫描和连接附近支持的健康设备。目前代码中已实现血压计数据同步,设备模型中预留了血糖仪、体重秤、血氧仪类型,但当前实际支持的数据解析以血压计为主。
|
||||
- 当您使用蓝牙设备功能时,我们会扫描和连接附近支持的健康设备。目前实际支持以采用标准蓝牙血压服务的血压计为主;其他设备类型将在功能正式开放后另行说明。
|
||||
- 我们会在本地保存已绑定设备的设备标识、设备名称、设备类型、服务 UUID、最近同步时间,以及用于避免重复同步的短期记录指纹。
|
||||
- 通过血压计同步时,我们会读取并记录收缩压、舒张压、脉搏、测量时间等数据。
|
||||
|
||||
@@ -4522,7 +4522,7 @@ class StaticTextPage extends ConsumerWidget {
|
||||
|
||||
### 一、服务内容
|
||||
|
||||
小脉健康为用户提供健康数据记录、健康档案管理、用药管理、饮食识别、运动计划、健康日历、检查报告管理、AI 健康解释、在线医生咨询、随访和蓝牙健康设备同步等功能。具体服务内容会根据产品版本、实际开通情况、监管要求和运营安排进行调整。
|
||||
小脉健康为用户提供健康数据记录、健康档案管理、用药管理、饮食识别、运动计划、健康日历、检查报告管理、AI 健康解释和蓝牙健康设备同步等功能。具体服务内容会根据产品版本、实际开通情况、监管要求和运营安排进行调整。
|
||||
|
||||
本应用当前可能包含以下服务:
|
||||
|
||||
@@ -4532,7 +4532,6 @@ class StaticTextPage extends ConsumerWidget {
|
||||
- 饮食识别:上传或拍摄饮食图片,识别食物种类、估算份量和热量。
|
||||
- AI 健康咨询:根据您输入的文字、图片、PDF 或健康档案,提供健康解释和管理建议。
|
||||
- 用药、运动和日历提醒:帮助您记录计划、查看进度和接收站内提醒。
|
||||
- 在线医生咨询:在开通范围内与医生或相关服务人员进行消息沟通。
|
||||
|
||||
### 二、账号注册与使用
|
||||
|
||||
@@ -4551,7 +4550,6 @@ class StaticTextPage extends ConsumerWidget {
|
||||
- 检查报告解读仅是对报告文字、指标或图片内容的辅助说明,不能替代医生结合病史、体格检查、影像资料和线下检查作出的诊断。
|
||||
- 饮食识别、热量估算、运动建议和健康评分可能存在误差,仅作为参考。
|
||||
- 用药相关内容不应替代医生、药师或说明书建议。请勿根据 App 内容自行开始、停止、更换或调整药物。
|
||||
- 医生咨询、随访和报告审核等服务,应结合线下诊疗意见综合判断。
|
||||
|
||||
如您出现胸痛、胸闷憋气、呼吸困难、意识异常、晕厥、言语不清、一侧肢体无力、严重头痛、严重过敏、血压或血糖明显异常等紧急情况,请立即拨打急救电话或前往医疗机构就诊,不应等待或依赖本应用回复。
|
||||
|
||||
@@ -4684,7 +4682,7 @@ class StaticTextPage extends ConsumerWidget {
|
||||
style: const TextStyle(
|
||||
fontSize: 19,
|
||||
color: AppColors.textPrimary,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
centerTitle: true,
|
||||
@@ -4709,7 +4707,7 @@ class StaticTextPage extends ConsumerWidget {
|
||||
h1Padding: const EdgeInsets.only(bottom: 18),
|
||||
h1: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
height: 1.35,
|
||||
letterSpacing: 0,
|
||||
@@ -4717,7 +4715,7 @@ class StaticTextPage extends ConsumerWidget {
|
||||
h2Padding: const EdgeInsets.only(top: 18, bottom: 8),
|
||||
h2: const TextStyle(
|
||||
fontSize: 19,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
height: 1.45,
|
||||
letterSpacing: 0,
|
||||
@@ -4725,7 +4723,7 @@ class StaticTextPage extends ConsumerWidget {
|
||||
h3Padding: const EdgeInsets.only(top: 16, bottom: 6),
|
||||
h3: const TextStyle(
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
height: 1.45,
|
||||
letterSpacing: 0,
|
||||
|
||||
@@ -866,7 +866,7 @@ class _ReportListPageState extends ConsumerState<ReportListPage> {
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: AppTextStyles.listTitle.copyWith(
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
|
||||
@@ -465,7 +465,7 @@ class _HealthMetricSheetState extends State<_HealthMetricSheet> {
|
||||
'选择提醒指标',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -114,7 +114,7 @@ class SettingsPage extends ConsumerWidget {
|
||||
minimumSize: const Size.fromHeight(52),
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: AppRadius.lgBorder,
|
||||
@@ -134,7 +134,7 @@ class SettingsPage extends ConsumerWidget {
|
||||
minimumSize: const Size.fromHeight(52),
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: AppRadius.lgBorder,
|
||||
|
||||
Reference in New Issue
Block a user