Commit 962df483813238ecb9542f4a02c388fd5e8556f5

Authored by 常守达
1 parent 1ac8d16e

feat(today): 初始资源颜色

Showing 57 changed files with 2157 additions and 212 deletions

230 Bytes | W: | H:

139 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin

224 Bytes | W: | H:

142 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin

842 Bytes | W: | H:

799 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
@@ -54,7 +54,7 @@ EXTERNAL SOURCES: @@ -54,7 +54,7 @@ EXTERNAL SOURCES:
54 :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" 54 :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin"
55 55
56 SPEC CHECKSUMS: 56 SPEC CHECKSUMS:
57 - Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 57 + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
58 image_cropper: c4326ea50132b1e1564499e5d32a84f01fb03537 58 image_cropper: c4326ea50132b1e1564499e5d32a84f01fb03537
59 image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a 59 image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
60 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 60 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
  1 +import 'package:doublefeel_flutter/core/theme/app_theme.dart';
1 import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; 2 import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
2 import 'package:flutter/material.dart'; 3 import 'package:flutter/material.dart';
3 import 'package:get/get.dart'; 4 import 'package:get/get.dart';
@@ -11,6 +12,9 @@ class DoubleFeelApp extends StatelessWidget { @@ -11,6 +12,9 @@ class DoubleFeelApp extends StatelessWidget {
11 Widget build(BuildContext context) { 12 Widget build(BuildContext context) {
12 return GetMaterialApp( 13 return GetMaterialApp(
13 title: context.l10n.appName, 14 title: context.l10n.appName,
  15 + theme: AppTheme.lightTheme,
  16 + darkTheme: AppTheme.darkTheme,
  17 + themeMode: ThemeMode.light,
14 initialRoute: AppPages.initialRoute, 18 initialRoute: AppPages.initialRoute,
15 getPages: AppPages.routes, 19 getPages: AppPages.routes,
16 localizationsDelegates: AppLocalizations.localizationsDelegates, 20 localizationsDelegates: AppLocalizations.localizationsDelegates,
@@ -2,15 +2,19 @@ import 'package:get/get.dart'; @@ -2,15 +2,19 @@ import 'package:get/get.dart';
2 2
3 import '../controllers/home_controller.dart'; 3 import '../controllers/home_controller.dart';
4 import '../controllers/today_controller.dart'; 4 import '../controllers/today_controller.dart';
  5 +import '../controllers/trend/trend_controller.dart';
  6 +import '../controllers/trend/hrv_controller.dart';
  7 +import '../controllers/trend/activity_controller.dart';
  8 +import '../controllers/trend/sleep_controller.dart';
5 9
6 class HomeBinding extends Bindings { 10 class HomeBinding extends Bindings {
7 @override 11 @override
8 void dependencies() { 12 void dependencies() {
9 Get.lazyPut<HomeController>(() => HomeController(), fenix: true); 13 Get.lazyPut<HomeController>(() => HomeController(), fenix: true);
10 Get.lazyPut<TodayController>(() => TodayController(), fenix: true); 14 Get.lazyPut<TodayController>(() => TodayController(), fenix: true);
11 - // 其他 tab 的 controller 后续在此添加  
12 - // Get.lazyPut<TrendController>(() => TrendController(), fenix: true);  
13 - // Get.lazyPut<FriendsController>(() => FriendsController(), fenix: true);  
14 - // Get.lazyPut<MyController>(() => MyController(), fenix: true); 15 + Get.lazyPut<TrendController>(() => TrendController(), fenix: true);
  16 + Get.lazyPut<HrvController>(() => HrvController(), fenix: true);
  17 + Get.lazyPut<ActivityController>(() => ActivityController(), fenix: true);
  18 + Get.lazyPut<SleepController>(() => SleepController(), fenix: true);
15 } 19 }
16 } 20 }
  1 +import 'package:get/get.dart';
  2 +import 'trend_period_controller.dart';
  3 +
  4 +/// 活动消耗 专属 Controller
  5 +class ActivityController extends TrendPeriodController {
  6 + // ── 响应式数据源 ──────────────────────────────────────
  7 + final totalBurn = '0'.obs;
  8 + final averageBurn = '0'.obs;
  9 + final chartData = <double>[].obs;
  10 + final chartLabels = <String>[].obs;
  11 +
  12 + @override
  13 + void loadData() {
  14 + isLoading.value = true;
  15 +
  16 + // 模拟 API 延迟加载
  17 + Future.delayed(const Duration(milliseconds: 300), () {
  18 + final offset = dateOffset.value;
  19 +
  20 + // 依据时间范围偏移动态渲染不同数据,模拟真实 API 拉取效果
  21 + totalBurn.value = '${2340 + offset * 120}';
  22 + averageBurn.value = '${334 + offset * 18}';
  23 +
  24 + chartData.value = [
  25 + 280.0 + offset * 15,
  26 + 350.0 - offset * 25,
  27 + 420.0 + offset * 35,
  28 + 310.0 - offset * 15,
  29 + 390.0 + offset * 20,
  30 + 480.0 + offset * 45,
  31 + 260.0 - offset * 30,
  32 + ].map((e) => e.clamp(50.0, 600.0)).toList();
  33 +
  34 + chartLabels.value = ['一', '二', '三', '四', '五', '六', '日'];
  35 +
  36 + isLoading.value = false;
  37 + });
  38 + }
  39 +}
  1 +import 'package:get/get.dart';
  2 +import 'trend_period_controller.dart';
  3 +
  4 +/// HRV 心率变异性 专属 Controller
  5 +class HrvController extends TrendPeriodController {
  6 + // ── 响应式数据源 ──────────────────────────────────────
  7 + final averageHrv = '0'.obs;
  8 + final changeHrv = '0'.obs;
  9 + final chartData = <double>[].obs;
  10 + final chartLabels = <String>[].obs;
  11 +
  12 + @override
  13 + void loadData() {
  14 + isLoading.value = true;
  15 +
  16 + // 模拟 API 延迟加载
  17 + Future.delayed(const Duration(milliseconds: 300), () {
  18 + final offset = dateOffset.value;
  19 +
  20 + // 依据时间范围偏移动态渲染不同数据,模拟真实 API 拉取效果
  21 + averageHrv.value = '${46 + offset}';
  22 + changeHrv.value = offset >= 0 ? '+3' : '${offset * 2}';
  23 +
  24 + chartData.value = [
  25 + 42.0 + offset,
  26 + 48.0 - offset,
  27 + 55.0 + offset * 2,
  28 + 46.0 - offset,
  29 + 52.0 + offset,
  30 + 58.0 + offset * 3,
  31 + 50.0 - offset * 2,
  32 + ].map((e) => e.clamp(20.0, 80.0)).toList();
  33 +
  34 + chartLabels.value = ['一', '二', '三', '四', '五', '六', '日'];
  35 +
  36 + isLoading.value = false;
  37 + });
  38 + }
  39 +}
  1 +import 'package:get/get.dart';
  2 +import 'trend_period_controller.dart';
  3 +
  4 +/// 睡眠报告 专属 Controller
  5 +class SleepController extends TrendPeriodController {
  6 + // ── 响应式数据源 ──────────────────────────────────────
  7 + final averageSleep = '0.0'.obs;
  8 + final deepSleepRatio = '0'.obs;
  9 + final chartData = <double>[].obs;
  10 + final chartLabels = <String>[].obs;
  11 +
  12 + @override
  13 + void loadData() {
  14 + isLoading.value = true;
  15 +
  16 + // 模拟 API 延迟加载
  17 + Future.delayed(const Duration(milliseconds: 300), () {
  18 + final offset = dateOffset.value;
  19 +
  20 + // 依据时间范围偏移动态渲染不同数据,模拟真实 API 拉取效果
  21 + final avg = (7.2 + offset * 0.15).clamp(4.0, 10.0);
  22 + averageSleep.value = avg.toStringAsFixed(1);
  23 + deepSleepRatio.value = '${(28 + offset).clamp(10, 50)}';
  24 +
  25 + chartData.value = [
  26 + 6.5 + offset * 0.1,
  27 + 7.0 - offset * 0.2,
  28 + 8.2 + offset * 0.3,
  29 + 6.8 - offset * 0.1,
  30 + 7.5 + offset * 0.2,
  31 + 9.0 + offset * 0.4,
  32 + 7.2 - offset * 0.3,
  33 + ].map((e) => e.clamp(4.0, 10.0)).toList();
  34 +
  35 + chartLabels.value = ['一', '二', '三', '四', '五', '六', '日'];
  36 +
  37 + isLoading.value = false;
  38 + });
  39 + }
  40 +}
  1 +import 'package:get/get.dart';
  2 +
  3 +/// 趋势页顶层 Controller,仅负责:
  4 +/// 顶层 HRV / 活动 / 睡眠 类型切换 (selectedTypeIndex)
  5 +class TrendController extends GetxController {
  6 + // 第1层类型 Tab(0 = HRV, 1 = 活动, 2 = 睡眠)
  7 + final selectedTypeIndex = 0.obs;
  8 +
  9 + void changeType(int index) {
  10 + if (index == selectedTypeIndex.value) return;
  11 + selectedTypeIndex.value = index;
  12 + }
  13 +}
  1 +import 'package:flutter/foundation.dart';
  2 +import 'package:get/get.dart';
  3 +
  4 +import 'trend_types.dart';
  5 +
  6 +/// TrendPeriodController — 抽象基类
  7 +///
  8 +/// 封装 HRV / 活动 / 睡眠 三个 Controller 共用的:
  9 +/// - 第2层:周 / 月 / 年 period 切换
  10 +/// - 第3层:日期范围偏移(每个 period 各自记忆 offset)
  11 +/// - isLoading 状态
  12 +/// - dateRangeLabel 计算
  13 +///
  14 +/// 子类只需实现 [loadData()],专注自己的数据逻辑。
  15 +abstract class TrendPeriodController extends GetxController {
  16 + // ── 第2层:时间维度 ──────────────────────────────────
  17 + final currentPeriod = TrendPeriod.week.obs;
  18 +
  19 + // ── 第3层:日期偏移(每个 period 独立记忆)──────────
  20 + // key = TrendPeriod,value = offset(0=当前,-1=上一个…)
  21 + final _offsetByPeriod = <TrendPeriod, int>{};
  22 + final dateOffset = 0.obs;
  23 +
  24 + // ── 加载状态 ─────────────────────────────────────────
  25 + final isLoading = false.obs;
  26 +
  27 + // ────────────────────────────────────────────────────
  28 + // 公开 API
  29 + // ────────────────────────────────────────────────────
  30 +
  31 + void changePeriod(TrendPeriod period) {
  32 + if (currentPeriod.value == period) return;
  33 + // 保存当前 period 的 offset,再切换
  34 + _offsetByPeriod[currentPeriod.value] = dateOffset.value;
  35 + currentPeriod.value = period;
  36 + // 恢复新 period 上次记忆 of offset(默认 0)
  37 + dateOffset.value = _offsetByPeriod[period] ?? 0;
  38 + loadData();
  39 + }
  40 +
  41 + void goToPrevious() {
  42 + final key = currentPeriod.value;
  43 + _offsetByPeriod[key] = (_offsetByPeriod[key] ?? 0) - 1;
  44 + dateOffset.value = _offsetByPeriod[key]!;
  45 + loadData();
  46 + }
  47 +
  48 + void goToNext() {
  49 + if (!canGoNext) return;
  50 + final key = currentPeriod.value;
  51 + _offsetByPeriod[key] = (_offsetByPeriod[key] ?? 0) + 1;
  52 + dateOffset.value = _offsetByPeriod[key]!;
  53 + loadData();
  54 + }
  55 +
  56 + bool get canGoNext => dateOffset.value < 0;
  57 +
  58 + /// 当前 period + offset 对应的日期显示文字
  59 + String get dateRangeLabel =>
  60 + buildDateRangeLabel(currentPeriod.value, dateOffset.value);
  61 +
  62 + // ────────────────────────────────────────────────────
  63 + // 子类必须实现:各自的数据加载逻辑
  64 + // ────────────────────────────────────────────────────
  65 +
  66 + @protected
  67 + void loadData();
  68 +
  69 + @override
  70 + void onInit() {
  71 + super.onInit();
  72 + loadData();
  73 + }
  74 +}
  1 +import 'package:intl/intl.dart';
  2 +
  3 +/// 时间维度枚举(共享给所有 trend controller 使用)
  4 +enum TrendPeriod { week, month, year }
  5 +
  6 +extension TrendPeriodLabel on TrendPeriod {
  7 + String get label => switch (this) {
  8 + TrendPeriod.week => '周',
  9 + TrendPeriod.month => '月',
  10 + TrendPeriod.year => '年',
  11 + };
  12 +}
  13 +
  14 +/// 根据 period + offset 计算显示标签
  15 +String buildDateRangeLabel(TrendPeriod period, int offset) {
  16 + final now = DateTime.now();
  17 + switch (period) {
  18 + case TrendPeriod.week:
  19 + final monday = now.subtract(Duration(days: now.weekday - 1));
  20 + final start = monday.add(Duration(days: offset * 7));
  21 + final end = start.add(const Duration(days: 6));
  22 + final fmt = DateFormat('M月d日');
  23 + return '${fmt.format(start)} - ${fmt.format(end)}';
  24 + case TrendPeriod.month:
  25 + final month = DateTime(now.year, now.month + offset);
  26 + return DateFormat('yyyy年M月').format(month);
  27 + case TrendPeriod.year:
  28 + return '${now.year + offset}年';
  29 + }
  30 +}
@@ -175,14 +175,6 @@ class TodayTab extends GetView<TodayController> { @@ -175,14 +175,6 @@ class TodayTab extends GetView<TodayController> {
175 const TodayHrvAdBanner(), 175 const TodayHrvAdBanner(),
176 const SizedBox(height: 12), 176 const SizedBox(height: 12),
177 177
178 - // 2. 该日压力状态行  
179 - _SectionHeader(  
180 - title: '该日压力状态',  
181 - showInfo: true,  
182 - onInfoTap: () {},  
183 - ),  
184 - const SizedBox(height: 8),  
185 -  
186 // 3. HRV + 心率数字卡 178 // 3. HRV + 心率数字卡
187 const TodayHrvNumberCard(), 179 const TodayHrvNumberCard(),
188 const SizedBox(height: 12), 180 const SizedBox(height: 12),
@@ -190,16 +182,27 @@ class TodayTab extends GetView<TodayController> { @@ -190,16 +182,27 @@ class TodayTab extends GetView<TodayController> {
190 // 4. HRV 趋势图 182 // 4. HRV 趋势图
191 const TodayHrvChartCard(), 183 const TodayHrvChartCard(),
192 const SizedBox(height: 12), 184 const SizedBox(height: 12),
193 - 185 + Container(
  186 + padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
  187 + margin: const EdgeInsets.only(bottom: 12),
  188 + decoration: BoxDecoration(
  189 + color: Colors.white,
  190 + borderRadius: BorderRadius.circular(16),
  191 + ),
  192 + child: Text(
  193 + '立即测量HRV',
  194 + style: TextStyle(
  195 + color: const Color(0xFF0F0F11),
  196 + fontSize: 16,
  197 + fontWeight: FontWeight.w600,
  198 + ),
  199 + ),
  200 + ),
194 // 5. 每日行动 201 // 5. 每日行动
195 const TodayActionsCard(), 202 const TodayActionsCard(),
196 203
197 - const TodayHrvChartCard(),  
198 - const TodayHrvChartCard(),  
199 - const TodayHrvChartCard(),  
200 - const TodayHrvChartCard(),  
201 // 底部安全间距(留给 TabBar 高度) 204 // 底部安全间距(留给 TabBar 高度)
202 - const SizedBox(height: 80), 205 + const SizedBox(height: 180),
203 ]), 206 ]),
204 ), 207 ),
205 ), 208 ),
@@ -288,43 +291,3 @@ class TodayTab extends GetView<TodayController> { @@ -288,43 +291,3 @@ class TodayTab extends GetView<TodayController> {
288 bool _isSameDay(DateTime a, DateTime b) => 291 bool _isSameDay(DateTime a, DateTime b) =>
289 a.year == b.year && a.month == b.month && a.day == b.day; 292 a.year == b.year && a.month == b.month && a.day == b.day;
290 } 293 }
291 -  
292 -/// 通用 Section 标题行  
293 -class _SectionHeader extends StatelessWidget {  
294 - final String title;  
295 - final bool showInfo;  
296 - final VoidCallback? onInfoTap;  
297 -  
298 - const _SectionHeader({  
299 - required this.title,  
300 - this.showInfo = false,  
301 - this.onInfoTap,  
302 - });  
303 -  
304 - @override  
305 - Widget build(BuildContext context) {  
306 - return Row(  
307 - children: [  
308 - Text(  
309 - title,  
310 - style: const TextStyle(  
311 - fontSize: 15,  
312 - fontWeight: FontWeight.w600,  
313 - color: Color(0xFF0F0F11),  
314 - ),  
315 - ),  
316 - if (showInfo) ...[  
317 - const SizedBox(width: 4),  
318 - GestureDetector(  
319 - onTap: onInfoTap,  
320 - child: const Icon(  
321 - Icons.info_outline,  
322 - size: 15,  
323 - color: Color(0xFF999999),  
324 - ),  
325 - ),  
326 - ],  
327 - ],  
328 - );  
329 - }  
330 -}  
1 import 'package:flutter/material.dart'; 1 import 'package:flutter/material.dart';
  2 +import 'package:get/get.dart';
2 3
3 -class TrendTab extends StatelessWidget { 4 +import '../../controllers/trend/trend_controller.dart';
  5 +import '../../widgets/trend/trend_type_tab_bar.dart';
  6 +import '../../widgets/trend/hrv_trend_view.dart';
  7 +import '../../widgets/trend/activity_trend_view.dart';
  8 +import '../../widgets/trend/sleep_trend_view.dart';
  9 +
  10 +/// 趋势大 Tab 页面主视图
  11 +/// 采用 StatefulWidget 绑定 SingleTickerProviderStateMixin 并通过 TabController
  12 +/// 控制 TabBar 与 TabBarView 的同步滑动,实现极具高级感的跟手划动效果。
  13 +class TrendTab extends StatefulWidget {
4 const TrendTab({super.key}); 14 const TrendTab({super.key});
5 15
6 @override 16 @override
  17 + State<TrendTab> createState() => _TrendTabState();
  18 +}
  19 +
  20 +class _TrendTabState extends State<TrendTab> with SingleTickerProviderStateMixin {
  21 + late final TabController _tabController;
  22 + late final TrendController _trendController;
  23 + late final Worker _rxWorker;
  24 +
  25 + static const _bgColor = Color(0xFFF5F2FF);
  26 + static const _h1 = Color(0xFF0F0F11);
  27 +
  28 + // 三个内容区(包裹 KeepAliveWrapper 以记忆滑动/图表状态,确保子 View 能继续保持纯净的 GetView 规范)
  29 + static const _contentViews = [
  30 + KeepAliveWrapper(child: HrvTrendView()),
  31 + KeepAliveWrapper(child: ActivityTrendView()),
  32 + KeepAliveWrapper(child: SleepTrendView()),
  33 + ];
  34 +
  35 + @override
  36 + void initState() {
  37 + super.initState();
  38 + _trendController = Get.find<TrendController>();
  39 +
  40 + // 初始化 TabController
  41 + _tabController = TabController(
  42 + length: 3,
  43 + vsync: this,
  44 + initialIndex: _trendController.selectedTypeIndex.value,
  45 + );
  46 +
  47 + // 1. 当滑动/点击切换页面时,同步更新 TrendController 状态
  48 + _tabController.addListener(() {
  49 + if (!_tabController.indexIsChanging) {
  50 + _trendController.changeType(_tabController.index);
  51 + }
  52 + });
  53 +
  54 + // 2. 当外部修改 selectedTypeIndex 时,同步动画跳转 TabBarView
  55 + _rxWorker = ever(_trendController.selectedTypeIndex, (index) {
  56 + if (_tabController.index != index) {
  57 + _tabController.animateTo(index);
  58 + }
  59 + });
  60 + }
  61 +
  62 + @override
  63 + void dispose() {
  64 + _rxWorker.dispose();
  65 + _tabController.dispose();
  66 + super.dispose();
  67 + }
  68 +
  69 + @override
7 Widget build(BuildContext context) { 70 Widget build(BuildContext context) {
8 - return const Center(  
9 - child: Text('趋势', style: TextStyle(fontSize: 20, color: Color(0xFF0F0F11))), 71 + final topPadding = MediaQuery.of(context).padding.top;
  72 +
  73 + return Container(
  74 + color: _bgColor,
  75 + child: Column(
  76 + children: [
  77 + // ── 状态栏占位 + 标题栏 ──
  78 + Container(
  79 + color: _bgColor,
  80 + padding: EdgeInsets.only(top: topPadding),
  81 + child: _buildAppBar(),
  82 + ),
  83 +
  84 + // ── 第1层:原生 TabBar(支持跟手滑动指示器) ──
  85 + Container(
  86 + color: _bgColor,
  87 + child: TrendTypeTabBar(tabController: _tabController),
  88 + ),
  89 +
  90 + // ── 内容区:TabBarView 提供极其平滑、跟手的原生翻页动画 ──
  91 + Expanded(
  92 + child: TabBarView(
  93 + controller: _tabController,
  94 + children: _contentViews,
  95 + ),
  96 + ),
  97 + ],
  98 + ),
  99 + );
  100 + }
  101 +
  102 + Widget _buildAppBar() {
  103 + return SizedBox(
  104 + height: 48,
  105 + child: const Padding(
  106 + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
  107 + child: Row(
  108 + children: [
  109 + Text(
  110 + '趋势',
  111 + style: TextStyle(
  112 + color: _h1,
  113 + fontSize: 24,
  114 + fontWeight: FontWeight.w600,
  115 + ),
  116 + ),
  117 + ],
  118 + ),
  119 + ),
10 ); 120 );
11 } 121 }
12 } 122 }
  123 +
  124 +/// 专用于在 TabBarView/PageView 中保持子组件状态的轻量级包装器
  125 +/// 使得子 View 可以维持无状态的 GetView 极简设计规范
  126 +class KeepAliveWrapper extends StatefulWidget {
  127 + final Widget child;
  128 + const KeepAliveWrapper({super.key, required this.child});
  129 +
  130 + @override
  131 + State<KeepAliveWrapper> createState() => _KeepAliveWrapperState();
  132 +}
  133 +
  134 +class _KeepAliveWrapperState extends State<KeepAliveWrapper>
  135 + with AutomaticKeepAliveClientMixin {
  136 + @override
  137 + bool get wantKeepAlive => true;
  138 +
  139 + @override
  140 + Widget build(BuildContext context) {
  141 + super.build(context);
  142 + return widget.child;
  143 + }
  144 +}
@@ -47,7 +47,6 @@ class _ActionCard extends StatelessWidget { @@ -47,7 +47,6 @@ class _ActionCard extends StatelessWidget {
47 47
48 const _ActionCard({required this.item}); 48 const _ActionCard({required this.item});
49 49
50 - static const _cardBg = Color(0xFFF7F7F9);  
51 static const _brandColor = Color(0xFF845EEE); 50 static const _brandColor = Color(0xFF845EEE);
52 static const _h1 = Color(0xFF0F0F11); 51 static const _h1 = Color(0xFF0F0F11);
53 static const _h2 = Color(0xFF666666); 52 static const _h2 = Color(0xFF666666);
@@ -58,7 +57,7 @@ class _ActionCard extends StatelessWidget { @@ -58,7 +57,7 @@ class _ActionCard extends StatelessWidget {
58 width: 140, 57 width: 140,
59 padding: const EdgeInsets.all(14), 58 padding: const EdgeInsets.all(14),
60 decoration: BoxDecoration( 59 decoration: BoxDecoration(
61 - color: _cardBg, 60 + color: Colors.white,
62 borderRadius: BorderRadius.circular(16), 61 borderRadius: BorderRadius.circular(16),
63 ), 62 ),
64 child: Column( 63 child: Column(
@@ -14,11 +14,33 @@ import '../../controllers/today_controller.dart'; @@ -14,11 +14,33 @@ import '../../controllers/today_controller.dart';
14 class TodayHrvChartCard extends GetView<TodayController> { 14 class TodayHrvChartCard extends GetView<TodayController> {
15 const TodayHrvChartCard({super.key}); 15 const TodayHrvChartCard({super.key});
16 16
17 - static const _cardBg = Color(0xFFF7F7F9);  
18 - 17 + static const _h2 = Color(0xFF78787D);
19 static const _h3 = Color(0xFF999999); 18 static const _h3 = Color(0xFF999999);
20 static const _h5 = Color(0xFFCCCCCC); 19 static const _h5 = Color(0xFFCCCCCC);
21 20
  21 + /// 实时压力 mock:按小时 0~18,Y 轴 0~80
  22 + static const List<double> _mockStressByHour = [
  23 + 18,
  24 + 22,
  25 + 28,
  26 + 35,
  27 + 32,
  28 + 40,
  29 + 48,
  30 + 55,
  31 + 62,
  32 + 58,
  33 + 52,
  34 + 45,
  35 + 38,
  36 + 42,
  37 + 50,
  38 + 56,
  39 + 64,
  40 + 58,
  41 + 46,
  42 + ];
  43 +
22 @override 44 @override
23 Widget build(BuildContext context) { 45 Widget build(BuildContext context) {
24 final spots = 46 final spots =
@@ -27,7 +49,7 @@ class TodayHrvChartCard extends GetView<TodayController> { @@ -27,7 +49,7 @@ class TodayHrvChartCard extends GetView<TodayController> {
27 return Container( 49 return Container(
28 padding: const EdgeInsets.fromLTRB(20, 20, 20, 16), 50 padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
29 decoration: BoxDecoration( 51 decoration: BoxDecoration(
30 - color: _cardBg, 52 + color: Colors.white,
31 borderRadius: BorderRadius.circular(16), 53 borderRadius: BorderRadius.circular(16),
32 ), 54 ),
33 child: Column( 55 child: Column(
@@ -48,16 +70,16 @@ class TodayHrvChartCard extends GetView<TodayController> { @@ -48,16 +70,16 @@ class TodayHrvChartCard extends GetView<TodayController> {
48 const Spacer(), 70 const Spacer(),
49 ], 71 ],
50 ), 72 ),
51 - const SizedBox(height: 16), 73 + const SizedBox(width: 14),
52 SizedBox( 74 SizedBox(
53 - height: 160, 75 + height: 174,
54 child: LineChart( 76 child: LineChart(
55 LineChartData( 77 LineChartData(
56 minX: 0, 78 minX: 0,
57 maxX: 18, 79 maxX: 18,
58 minY: 0, 80 minY: 0,
59 maxY: 80, 81 maxY: 80,
60 - clipData: const FlClipData.all(), 82 + // clipData: const FlClipData.all(),
61 gridData: FlGridData( 83 gridData: FlGridData(
62 show: true, 84 show: true,
63 drawVerticalLine: true, 85 drawVerticalLine: true,
@@ -118,7 +140,7 @@ class TodayHrvChartCard extends GetView<TodayController> { @@ -118,7 +140,7 @@ class TodayHrvChartCard extends GetView<TodayController> {
118 radius: 4, 140 radius: 4,
119 color: Colors.white, 141 color: Colors.white,
120 strokeWidth: 3, 142 strokeWidth: 3,
121 - strokeColor: _getDotStrokeColor(spot), 143 + strokeColor: _getColor(spot.y),
122 ), 144 ),
123 )) 145 ))
124 ], 146 ],
@@ -138,7 +160,7 @@ class TodayHrvChartCard extends GetView<TodayController> { @@ -138,7 +160,7 @@ class TodayHrvChartCard extends GetView<TodayController> {
138 radius: 5, 160 radius: 5,
139 color: Colors.white, 161 color: Colors.white,
140 strokeWidth: 3.5, 162 strokeWidth: 3.5,
141 - strokeColor: _getDotStrokeColor(spot), 163 + strokeColor: _getColor(spot.y),
142 ); 164 );
143 }, 165 },
144 ), 166 ),
@@ -168,45 +190,218 @@ class TodayHrvChartCard extends GetView<TodayController> { @@ -168,45 +190,218 @@ class TodayHrvChartCard extends GetView<TodayController> {
168 ), 190 ),
169 ), 191 ),
170 ), 192 ),
  193 + Padding(
  194 + padding: const EdgeInsets.only(bottom: 14, top: 16),
  195 + child: Row(
  196 + children: [
  197 + const Text(
  198 + '实时压力',
  199 + style: TextStyle(
  200 + color: _h2,
  201 + fontSize: 14,
  202 + fontWeight: FontWeight.w600,
  203 + ),
  204 + ),
  205 + const SizedBox(width: 4),
  206 + const Icon(Icons.info_outline, size: 14, color: _h3),
  207 + const Spacer(),
  208 + ],
  209 + ),
  210 + ),
  211 + SizedBox(
  212 + height: 174,
  213 + child: Stack(
  214 + children: [
  215 + BarChart(
  216 + BarChartData(
  217 + minY: 0,
  218 + maxY: 100,
  219 + groupsSpace: 1,
  220 + alignment: BarChartAlignment.start,
  221 + barGroups: List.generate(
  222 + _mockStressByHour.length * 5,
  223 + (i) => BarChartGroupData(
  224 + x: i,
  225 + barRods: [
  226 + BarChartRodData(
  227 + toY:
  228 + _mockStressByHour[i % _mockStressByHour.length],
  229 + width: 2,
  230 + color: _getColor(_mockStressByHour[
  231 + i % _mockStressByHour.length]),
  232 + borderRadius: const BorderRadius.only(
  233 + topLeft: Radius.circular(2),
  234 + topRight: Radius.circular(2),
  235 + ),
  236 + ),
  237 + ],
  238 + ),
  239 + ),
  240 + gridData: FlGridData(
  241 + show: true,
  242 + drawVerticalLine: false,
  243 + drawHorizontalLine: true,
  244 + horizontalInterval: 25,
  245 + getDrawingHorizontalLine: (_) => const FlLine(
  246 + color: _h5,
  247 + strokeWidth: 1,
  248 + dashArray: [2, 2],
  249 + ),
  250 + ),
  251 + borderData: FlBorderData(show: false),
  252 + titlesData: FlTitlesData(
  253 + topTitles: const AxisTitles(
  254 + sideTitles: SideTitles(showTitles: false),
  255 + ),
  256 + rightTitles: AxisTitles(
  257 + sideTitles: SideTitles(
  258 + showTitles: true,
  259 + getTitlesWidget: (val, _) {
  260 + return Text(
  261 + '${val.toInt()}',
  262 + style: const TextStyle(fontSize: 10, color: _h3),
  263 + );
  264 + },
  265 + )),
  266 + leftTitles: const AxisTitles(
  267 + sideTitles: SideTitles(showTitles: false),
  268 + ),
  269 + bottomTitles: AxisTitles(
  270 + sideTitles: SideTitles(
  271 + showTitles: true,
  272 + reservedSize: 20,
  273 + interval: 60,
  274 + getTitlesWidget: (val, meta) {
  275 + final labels = {
  276 + 0: '00:00',
  277 + 33: '06:00',
  278 + 66: '12:00',
  279 + 99: '18:00',
  280 + };
  281 + final label = labels[val];
  282 + if (label == null) return const SizedBox.shrink();
  283 + return SideTitleWidget(
  284 + meta: meta,
  285 + child: Text(
  286 + label,
  287 + style:
  288 + const TextStyle(fontSize: 10, color: _h3),
  289 + ),
  290 + );
  291 + },
  292 + ),
  293 + ),
  294 + ),
  295 + barTouchData: BarTouchData(
  296 + touchTooltipData: BarTouchTooltipData(
  297 + tooltipRoundedRadius: 8,
  298 + tooltipBorder: BorderSide.none,
  299 + getTooltipColor: (touchedSpot) => Color(0xFFF3F3F3),
  300 + tooltipPadding: EdgeInsets.only(
  301 + left: 12, right: 12, top: 6, bottom: 5),
  302 + getTooltipItem: (group, groupIndex, rod, rodIndex) {
  303 + return BarTooltipItem(
  304 + '',
  305 + TextStyle(
  306 + color: Colors.white,
  307 + fontWeight: FontWeight.bold,
  308 + ),
  309 + children: _getTooltip(rod),
  310 + textAlign: TextAlign.start,
  311 + );
  312 + })),
  313 + ),
  314 + ),
  315 + Container(
  316 + width: 74,
  317 + height: 160,
  318 + decoration: BoxDecoration(
  319 + gradient: LinearGradient(
  320 + begin: Alignment(0.50, -0.00),
  321 + end: Alignment(0.50, 1.00),
  322 + colors: [
  323 + const Color(0x4C835DED),
  324 + const Color(0x00845EEE)
  325 + ],
  326 + ),
  327 + ),
  328 + )
  329 + ],
  330 + ),
  331 + ),
171 ], 332 ],
172 ), 333 ),
173 ); 334 );
174 } 335 }
175 336
176 - Color _getDotStrokeColor(FlSpot spot) {  
177 - if (spot.y > 60) { 337 + Color _getColor(double value) {
  338 + if (value > 60) {
178 return const Color(0xFF3BD49D); 339 return const Color(0xFF3BD49D);
179 - } else if (spot.y > 50) { 340 + } else if (value > 50) {
180 return const Color(0xFF7B9BFB); 341 return const Color(0xFF7B9BFB);
181 - } else if (spot.y > 30) { 342 + } else if (value > 30) {
182 return const Color(0xFFFF9A6E); 343 return const Color(0xFFFF9A6E);
183 - } else if (spot.y > 20) { 344 + } else if (value > 20) {
184 return const Color(0xFFFF5279); 345 return const Color(0xFFFF5279);
185 } else { 346 } else {
186 return const Color(0xFF7B9BFB); 347 return const Color(0xFF7B9BFB);
187 } 348 }
188 } 349 }
189 350
190 - String _getDotStrokeStatus(FlSpot spot) {  
191 - if (spot.y > 60) { 351 + String _getStatus(double value) {
  352 + if (value > 60) {
192 return '状态优秀'; 353 return '状态优秀';
193 - } else if (spot.y > 50) { 354 + } else if (value > 50) {
194 return '状态良好'; 355 return '状态良好';
195 - } else if (spot.y > 30) { 356 + } else if (value > 30) {
196 return '状态一般'; 357 return '状态一般';
197 - } else if (spot.y > 20) { 358 + } else if (value > 20) {
198 return '状态较差'; 359 return '状态较差';
199 } else { 360 } else {
200 return '状态极差'; 361 return '状态极差';
201 } 362 }
202 } 363 }
203 364
  365 + List<TextSpan>? _getTooltip(BarChartRodData rod) {
  366 + return [
  367 + // WidgetSpan(
  368 + // child: Image.asset('assets/images/home/today/ic_tooltip_dot.png',
  369 + // width: 12, height: 12),
  370 + // ),
  371 + TextSpan(
  372 + text: _getStatus(rod.toY),
  373 + style: TextStyle(
  374 + color: _getColor(rod.toY),
  375 + fontWeight: FontWeight.bold,
  376 + ),
  377 + ),
  378 + TextSpan(
  379 + text: '\n',
  380 + style: TextStyle(
  381 + color: Colors.white,
  382 + fontWeight: FontWeight.bold,
  383 + ),
  384 + ),
  385 + TextSpan(
  386 + text: '${rod.toY}ms.${rod.fromY}:00',
  387 + style: TextStyle(
  388 + color: Color(0xFF78787D),
  389 + fontWeight: FontWeight.bold,
  390 + ),
  391 + ),
  392 + ];
  393 + }
  394 +
204 List<TextSpan>? _getTooltipChildren(LineBarSpot flSpot) { 395 List<TextSpan>? _getTooltipChildren(LineBarSpot flSpot) {
205 return [ 396 return [
  397 + // WidgetSpan(
  398 + // child: Image.asset('assets/images/home/today/ic_tooltip_dot.png',
  399 + // width: 12, height: 12),
  400 + // ),
206 TextSpan( 401 TextSpan(
207 - text: _getDotStrokeStatus(flSpot), 402 + text: _getStatus(flSpot.y),
208 style: TextStyle( 403 style: TextStyle(
209 - color: _getDotStrokeColor(flSpot), 404 + color: _getColor(flSpot.y),
210 fontWeight: FontWeight.bold, 405 fontWeight: FontWeight.bold,
211 ), 406 ),
212 ), 407 ),
@@ -7,14 +7,12 @@ import '../../controllers/today_controller.dart'; @@ -7,14 +7,12 @@ import '../../controllers/today_controller.dart';
7 class TodayHrvNumberCard extends GetView<TodayController> { 7 class TodayHrvNumberCard extends GetView<TodayController> {
8 const TodayHrvNumberCard({super.key}); 8 const TodayHrvNumberCard({super.key});
9 9
10 - static const _cardBg = Color(0xFFF7F7F9);  
11 -  
12 @override 10 @override
13 Widget build(BuildContext context) { 11 Widget build(BuildContext context) {
14 return Container( 12 return Container(
15 padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), 13 padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
16 decoration: BoxDecoration( 14 decoration: BoxDecoration(
17 - color: _cardBg, 15 + color: Colors.white,
18 borderRadius: BorderRadius.circular(16), 16 borderRadius: BorderRadius.circular(16),
19 ), 17 ),
20 child: Obx(() => Row( 18 child: Obx(() => Row(
@@ -28,11 +26,11 @@ class TodayHrvNumberCard extends GetView<TodayController> { @@ -28,11 +26,11 @@ class TodayHrvNumberCard extends GetView<TodayController> {
28 ), 26 ),
29 ), 27 ),
30 // 分割线 28 // 分割线
31 - Container(  
32 - width: 1,  
33 - height: 40,  
34 - color: const Color(0xFFE0E0E0),  
35 - ), 29 + // Container(
  30 + // width: 1,
  31 + // height: 40,
  32 + // color: const Color(0xFFE0E0E0),
  33 + // ),
36 // 心率 34 // 心率
37 Expanded( 35 Expanded(
38 child: _NumberItem( 36 child: _NumberItem(
  1 +import 'package:fl_chart/fl_chart.dart';
  2 +import 'package:flutter/material.dart';
  3 +import 'package:get/get.dart';
  4 +
  5 +import '../../controllers/trend/trend_types.dart';
  6 +import '../../controllers/trend/activity_controller.dart';
  7 +import 'trend_period_tab_bar.dart';
  8 +import 'trend_date_range_bar.dart';
  9 +
  10 +/// 活动消耗趋势内容区(含第2、3层 Tab)
  11 +class ActivityTrendView extends GetView<ActivityController> {
  12 + const ActivityTrendView({super.key});
  13 +
  14 + @override
  15 + Widget build(BuildContext context) {
  16 + return Column(
  17 + children: [
  18 + TrendPeriodTabBar(controller: controller),
  19 + TrendDateRangeBar(controller: controller),
  20 + Expanded(
  21 + child: Obx(() {
  22 + if (controller.isLoading.value) {
  23 + return const Center(
  24 + child: CircularProgressIndicator(
  25 + valueColor: AlwaysStoppedAnimation(Color(0xFF845EEE)),
  26 + ),
  27 + );
  28 + }
  29 + return SingleChildScrollView(
  30 + padding: const EdgeInsets.symmetric(horizontal: 16),
  31 + child: Column(
  32 + children: [
  33 + const SizedBox(height: 8),
  34 + const _ActivitySummaryCard(),
  35 + const SizedBox(height: 12),
  36 + const _ActivityChartCard(),
  37 + const SizedBox(height: 180),
  38 + ],
  39 + ),
  40 + );
  41 + }),
  42 + ),
  43 + ],
  44 + );
  45 + }
  46 +}
  47 +
  48 +// ─── 摘要卡片 ─────────────────────────────────────────
  49 +class _ActivitySummaryCard extends GetView<ActivityController> {
  50 + const _ActivitySummaryCard();
  51 +
  52 + static const _h1 = Color(0xFF0F0F11);
  53 + static const _h3 = Color(0xFF999999);
  54 + static const _activeColor = Color(0xFFFF9A6E);
  55 +
  56 + @override
  57 + Widget build(BuildContext context) {
  58 + return Container(
  59 + padding: const EdgeInsets.all(20),
  60 + decoration: BoxDecoration(
  61 + color: Colors.white,
  62 + borderRadius: BorderRadius.circular(16),
  63 + ),
  64 + child: Obx(() {
  65 + final periodLabel = controller.currentPeriod.value.label;
  66 + return Column(
  67 + crossAxisAlignment: CrossAxisAlignment.start,
  68 + children: [
  69 + Row(
  70 + children: [
  71 + const Text(
  72 + '活动消耗趋势',
  73 + style: TextStyle(
  74 + color: _h1,
  75 + fontSize: 16,
  76 + fontWeight: FontWeight.w600,
  77 + ),
  78 + ),
  79 + const SizedBox(width: 4),
  80 + const Icon(Icons.info_outline, size: 14, color: _h3),
  81 + ],
  82 + ),
  83 + const SizedBox(height: 16),
  84 + Row(
  85 + children: [
  86 + _StatItem(
  87 + label: '本$periodLabel总消耗',
  88 + value: controller.totalBurn.value,
  89 + unit: 'kcal',
  90 + color: _activeColor,
  91 + ),
  92 + const SizedBox(width: 24),
  93 + _StatItem(
  94 + label: '日均消耗',
  95 + value: controller.averageBurn.value,
  96 + unit: 'kcal',
  97 + color: const Color(0xFF3BD49D),
  98 + ),
  99 + ],
  100 + ),
  101 + ],
  102 + );
  103 + }),
  104 + );
  105 + }
  106 +}
  107 +
  108 +class _StatItem extends StatelessWidget {
  109 + final String label;
  110 + final String value;
  111 + final String unit;
  112 + final Color color;
  113 +
  114 + const _StatItem({
  115 + required this.label,
  116 + required this.value,
  117 + required this.unit,
  118 + required this.color,
  119 + });
  120 +
  121 + static const _h2 = Color(0xFF78787D);
  122 +
  123 + @override
  124 + Widget build(BuildContext context) {
  125 + return Column(
  126 + crossAxisAlignment: CrossAxisAlignment.start,
  127 + children: [
  128 + Text(label, style: const TextStyle(color: _h2, fontSize: 12)),
  129 + const SizedBox(height: 4),
  130 + Row(
  131 + crossAxisAlignment: CrossAxisAlignment.end,
  132 + children: [
  133 + Text(
  134 + value,
  135 + style: TextStyle(
  136 + color: color,
  137 + fontSize: 28,
  138 + fontWeight: FontWeight.w600,
  139 + height: 1.0,
  140 + ),
  141 + ),
  142 + const SizedBox(width: 2),
  143 + Padding(
  144 + padding: const EdgeInsets.only(bottom: 3),
  145 + child: Text(
  146 + unit,
  147 + style: TextStyle(color: color, fontSize: 12),
  148 + ),
  149 + ),
  150 + ],
  151 + ),
  152 + ],
  153 + );
  154 + }
  155 +}
  156 +
  157 +// ─── 活动柱状图卡片 ────────────────────────────────────
  158 +class _ActivityChartCard extends GetView<ActivityController> {
  159 + const _ActivityChartCard();
  160 +
  161 + static const _h1 = Color(0xFF0F0F11);
  162 + static const _h3 = Color(0xFF999999);
  163 + static const _h5 = Color(0xFFCCCCCC);
  164 + static const _activeColor = Color(0xFFFF9A6E);
  165 +
  166 + @override
  167 + Widget build(BuildContext context) {
  168 + return Container(
  169 + padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
  170 + decoration: BoxDecoration(
  171 + color: Colors.white,
  172 + borderRadius: BorderRadius.circular(16),
  173 + ),
  174 + child: Obx(() {
  175 + final period = controller.currentPeriod.value;
  176 + return Column(
  177 + crossAxisAlignment: CrossAxisAlignment.start,
  178 + children: [
  179 + Text(
  180 + '活动消耗${period.label}趋势图',
  181 + style: const TextStyle(
  182 + color: _h1,
  183 + fontSize: 14,
  184 + fontWeight: FontWeight.w600,
  185 + ),
  186 + ),
  187 + const SizedBox(height: 16),
  188 + SizedBox(
  189 + height: 180,
  190 + child: _buildChart(),
  191 + ),
  192 + ],
  193 + );
  194 + }),
  195 + );
  196 + }
  197 +
  198 + Widget _buildChart() {
  199 + if (controller.chartData.isEmpty) {
  200 + return const Center(
  201 + child: CircularProgressIndicator(
  202 + valueColor: AlwaysStoppedAnimation(Color(0xFFFF9A6E)),
  203 + ),
  204 + );
  205 + }
  206 +
  207 + return BarChart(
  208 + BarChartData(
  209 + minY: 0,
  210 + maxY: 600,
  211 + groupsSpace: 8,
  212 + alignment: BarChartAlignment.spaceAround,
  213 + barGroups: List.generate(
  214 + controller.chartData.length,
  215 + (i) => BarChartGroupData(
  216 + x: i,
  217 + barRods: [
  218 + BarChartRodData(
  219 + toY: controller.chartData[i],
  220 + width: 20,
  221 + color: _activeColor,
  222 + borderRadius: const BorderRadius.only(
  223 + topLeft: Radius.circular(4),
  224 + topRight: Radius.circular(4),
  225 + ),
  226 + ),
  227 + ],
  228 + ),
  229 + ),
  230 + gridData: FlGridData(
  231 + show: true,
  232 + drawVerticalLine: false,
  233 + horizontalInterval: 200,
  234 + getDrawingHorizontalLine: (_) => const FlLine(
  235 + color: _h5,
  236 + strokeWidth: 1,
  237 + dashArray: [2, 2],
  238 + ),
  239 + ),
  240 + borderData: FlBorderData(show: false),
  241 + titlesData: FlTitlesData(
  242 + topTitles: const AxisTitles(
  243 + sideTitles: SideTitles(showTitles: false)),
  244 + rightTitles: const AxisTitles(
  245 + sideTitles: SideTitles(showTitles: false)),
  246 + leftTitles: AxisTitles(
  247 + sideTitles: SideTitles(
  248 + showTitles: true,
  249 + reservedSize: 36,
  250 + interval: 200,
  251 + getTitlesWidget: (val, _) => Text(
  252 + '${val.toInt()}',
  253 + style: const TextStyle(fontSize: 10, color: _h3),
  254 + ),
  255 + ),
  256 + ),
  257 + bottomTitles: AxisTitles(
  258 + sideTitles: SideTitles(
  259 + showTitles: true,
  260 + reservedSize: 20,
  261 + getTitlesWidget: (val, _) {
  262 + final i = val.toInt();
  263 + if (i < 0 || i >= controller.chartLabels.length) {
  264 + return const SizedBox.shrink();
  265 + }
  266 + return Text(
  267 + controller.chartLabels[i],
  268 + style: const TextStyle(fontSize: 10, color: _h3),
  269 + );
  270 + },
  271 + ),
  272 + ),
  273 + ),
  274 + ),
  275 + );
  276 + }
  277 +}
  1 +import 'package:fl_chart/fl_chart.dart';
  2 +import 'package:flutter/material.dart';
  3 +import 'package:get/get.dart';
  4 +
  5 +import '../../controllers/trend/trend_types.dart';
  6 +import '../../controllers/trend/hrv_controller.dart';
  7 +import 'trend_period_tab_bar.dart';
  8 +import 'trend_date_range_bar.dart';
  9 +
  10 +/// HRV 趋势内容区(含第2、3层 Tab)
  11 +class HrvTrendView extends GetView<HrvController> {
  12 + const HrvTrendView({super.key});
  13 +
  14 + @override
  15 + Widget build(BuildContext context) {
  16 + return Column(
  17 + children: [
  18 + // 第2层:周/月/年
  19 + TrendPeriodTabBar(controller: controller),
  20 + // 第3层:日期范围
  21 + TrendDateRangeBar(controller: controller),
  22 + // 图表内容
  23 + Expanded(
  24 + child: Obx(() {
  25 + if (controller.isLoading.value) {
  26 + return const Center(
  27 + child: CircularProgressIndicator(
  28 + valueColor: AlwaysStoppedAnimation(Color(0xFF845EEE)),
  29 + ),
  30 + );
  31 + }
  32 + return SingleChildScrollView(
  33 + padding: const EdgeInsets.symmetric(horizontal: 16),
  34 + child: Column(
  35 + children: [
  36 + const SizedBox(height: 8),
  37 + const _HrvSummaryCard(),
  38 + const SizedBox(height: 12),
  39 + const _HrvChartCard(),
  40 + const SizedBox(height: 180),
  41 + ],
  42 + ),
  43 + );
  44 + }),
  45 + ),
  46 + ],
  47 + );
  48 + }
  49 +}
  50 +
  51 +// ─── 摘要卡片 ────────────────────────────────────────────
  52 +class _HrvSummaryCard extends GetView<HrvController> {
  53 + const _HrvSummaryCard();
  54 +
  55 + static const _h1 = Color(0xFF0F0F11);
  56 + static const _h3 = Color(0xFF999999);
  57 + static const _brandColor = Color(0xFF845EEE);
  58 +
  59 + @override
  60 + Widget build(BuildContext context) {
  61 + return Container(
  62 + padding: const EdgeInsets.all(20),
  63 + decoration: BoxDecoration(
  64 + color: Colors.white,
  65 + borderRadius: BorderRadius.circular(16),
  66 + ),
  67 + child: Obx(() {
  68 + final periodLabel = controller.currentPeriod.value.label;
  69 + return Column(
  70 + crossAxisAlignment: CrossAxisAlignment.start,
  71 + children: [
  72 + Row(
  73 + children: [
  74 + const Text(
  75 + 'HRV趋势',
  76 + style: TextStyle(
  77 + color: _h1,
  78 + fontSize: 16,
  79 + fontWeight: FontWeight.w600,
  80 + ),
  81 + ),
  82 + const SizedBox(width: 4),
  83 + const Icon(Icons.info_outline, size: 14, color: _h3),
  84 + ],
  85 + ),
  86 + const SizedBox(height: 16),
  87 + Row(
  88 + children: [
  89 + _StatItem(
  90 + label: '本$periodLabel平均',
  91 + value: controller.averageHrv.value,
  92 + unit: 'ms',
  93 + color: _brandColor,
  94 + ),
  95 + const SizedBox(width: 24),
  96 + _StatItem(
  97 + label: '较上$periodLabel',
  98 + value: controller.changeHrv.value,
  99 + unit: 'ms',
  100 + color: const Color(0xFF3BD49D),
  101 + ),
  102 + ],
  103 + ),
  104 + ],
  105 + );
  106 + }),
  107 + );
  108 + }
  109 +}
  110 +
  111 +class _StatItem extends StatelessWidget {
  112 + final String label;
  113 + final String value;
  114 + final String unit;
  115 + final Color color;
  116 +
  117 + const _StatItem({
  118 + required this.label,
  119 + required this.value,
  120 + required this.unit,
  121 + required this.color,
  122 + });
  123 +
  124 + static const _h2 = Color(0xFF78787D);
  125 +
  126 + @override
  127 + Widget build(BuildContext context) {
  128 + return Column(
  129 + crossAxisAlignment: CrossAxisAlignment.start,
  130 + children: [
  131 + Text(
  132 + label,
  133 + style: const TextStyle(color: _h2, fontSize: 12),
  134 + ),
  135 + const SizedBox(height: 4),
  136 + Row(
  137 + crossAxisAlignment: CrossAxisAlignment.end,
  138 + children: [
  139 + Text(
  140 + value,
  141 + style: TextStyle(
  142 + color: color,
  143 + fontSize: 32,
  144 + fontWeight: FontWeight.w600,
  145 + height: 1.0,
  146 + ),
  147 + ),
  148 + const SizedBox(width: 2),
  149 + Padding(
  150 + padding: const EdgeInsets.only(bottom: 4),
  151 + child: Text(
  152 + unit,
  153 + style: TextStyle(color: color, fontSize: 14),
  154 + ),
  155 + ),
  156 + ],
  157 + ),
  158 + ],
  159 + );
  160 + }
  161 +}
  162 +
  163 +// ─── HRV 折线图卡片 ──────────────────────────────────────
  164 +class _HrvChartCard extends GetView<HrvController> {
  165 + const _HrvChartCard();
  166 +
  167 + static const _h1 = Color(0xFF0F0F11);
  168 + static const _h3 = Color(0xFF999999);
  169 + static const _h5 = Color(0xFFCCCCCC);
  170 + static const _brandColor = Color(0xFF845EEE);
  171 +
  172 + @override
  173 + Widget build(BuildContext context) {
  174 + return Container(
  175 + padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
  176 + decoration: BoxDecoration(
  177 + color: Colors.white,
  178 + borderRadius: BorderRadius.circular(16),
  179 + ),
  180 + child: Obx(() {
  181 + final period = controller.currentPeriod.value;
  182 + return Column(
  183 + crossAxisAlignment: CrossAxisAlignment.start,
  184 + children: [
  185 + Text(
  186 + 'HRV ${period.label}趋势图',
  187 + style: const TextStyle(
  188 + color: _h1,
  189 + fontSize: 14,
  190 + fontWeight: FontWeight.w600,
  191 + ),
  192 + ),
  193 + const SizedBox(height: 16),
  194 + SizedBox(
  195 + height: 180,
  196 + child: _buildChart(period),
  197 + ),
  198 + ],
  199 + );
  200 + }),
  201 + );
  202 + }
  203 +
  204 + Widget _buildChart(TrendPeriod period) {
  205 + if (controller.chartData.isEmpty) {
  206 + return const Center(
  207 + child: CircularProgressIndicator(
  208 + valueColor: AlwaysStoppedAnimation(Color(0xFF845EEE)),
  209 + ),
  210 + );
  211 + }
  212 +
  213 + final spots = List.generate(
  214 + controller.chartData.length,
  215 + (i) => FlSpot(i.toDouble(), controller.chartData[i]),
  216 + );
  217 +
  218 + return LineChart(
  219 + LineChartData(
  220 + minX: 0,
  221 + maxX: (controller.chartData.length - 1).toDouble(),
  222 + minY: 20,
  223 + maxY: 80,
  224 + gridData: FlGridData(
  225 + show: true,
  226 + drawVerticalLine: false,
  227 + horizontalInterval: 20,
  228 + getDrawingHorizontalLine: (_) => const FlLine(
  229 + color: _h5,
  230 + strokeWidth: 1,
  231 + dashArray: [2, 2],
  232 + ),
  233 + ),
  234 + borderData: FlBorderData(show: false),
  235 + titlesData: FlTitlesData(
  236 + topTitles:
  237 + const AxisTitles(sideTitles: SideTitles(showTitles: false)),
  238 + rightTitles:
  239 + const AxisTitles(sideTitles: SideTitles(showTitles: false)),
  240 + leftTitles: AxisTitles(
  241 + sideTitles: SideTitles(
  242 + showTitles: true,
  243 + reservedSize: 30,
  244 + interval: 20,
  245 + getTitlesWidget: (val, _) => Text(
  246 + '${val.toInt()}',
  247 + style: const TextStyle(fontSize: 10, color: _h3),
  248 + ),
  249 + ),
  250 + ),
  251 + bottomTitles: AxisTitles(
  252 + sideTitles: SideTitles(
  253 + showTitles: true,
  254 + reservedSize: 20,
  255 + getTitlesWidget: (val, _) {
  256 + final i = val.toInt();
  257 + if (i < 0 || i >= controller.chartLabels.length) {
  258 + return const SizedBox.shrink();
  259 + }
  260 + return Text(
  261 + controller.chartLabels[i],
  262 + style: const TextStyle(fontSize: 10, color: _h3),
  263 + );
  264 + },
  265 + ),
  266 + ),
  267 + ),
  268 + lineBarsData: [
  269 + LineChartBarData(
  270 + spots: spots,
  271 + isCurved: true,
  272 + curveSmoothness: 0.3,
  273 + color: _brandColor,
  274 + barWidth: 2,
  275 + belowBarData: BarAreaData(
  276 + show: true,
  277 + gradient: LinearGradient(
  278 + begin: Alignment.topCenter,
  279 + end: Alignment.bottomCenter,
  280 + colors: [
  281 + _brandColor.withValues(alpha: 0.15),
  282 + _brandColor.withValues(alpha: 0.0),
  283 + ],
  284 + ),
  285 + ),
  286 + dotData: FlDotData(
  287 + getDotPainter: (spot, percent, barData, index) =>
  288 + FlDotCirclePainter(
  289 + radius: 4,
  290 + color: Colors.white,
  291 + strokeWidth: 2.5,
  292 + strokeColor: _brandColor,
  293 + ),
  294 + ),
  295 + ),
  296 + ],
  297 + lineTouchData: LineTouchData(
  298 + touchTooltipData: LineTouchTooltipData(
  299 + tooltipRoundedRadius: 8,
  300 + getTooltipColor: (_) => const Color(0xFFF3F3F3),
  301 + getTooltipItems: (spots) => spots.map((s) {
  302 + return LineTooltipItem(
  303 + '${s.y.toInt()}ms',
  304 + const TextStyle(
  305 + color: _brandColor,
  306 + fontWeight: FontWeight.w600,
  307 + fontSize: 12,
  308 + ),
  309 + );
  310 + }).toList(),
  311 + ),
  312 + ),
  313 + ),
  314 + );
  315 + }
  316 +}
  1 +import 'package:fl_chart/fl_chart.dart';
  2 +import 'package:flutter/material.dart';
  3 +import 'package:get/get.dart';
  4 +
  5 +import '../../controllers/trend/trend_types.dart';
  6 +import '../../controllers/trend/sleep_controller.dart';
  7 +import 'trend_period_tab_bar.dart';
  8 +import 'trend_date_range_bar.dart';
  9 +
  10 +/// 睡眠报告趋势内容区(含第2、3层 Tab)
  11 +class SleepTrendView extends GetView<SleepController> {
  12 + const SleepTrendView({super.key});
  13 +
  14 + @override
  15 + Widget build(BuildContext context) {
  16 + return Column(
  17 + children: [
  18 + TrendPeriodTabBar(controller: controller),
  19 + TrendDateRangeBar(controller: controller),
  20 + Expanded(
  21 + child: Obx(() {
  22 + if (controller.isLoading.value) {
  23 + return const Center(
  24 + child: CircularProgressIndicator(
  25 + valueColor: AlwaysStoppedAnimation(Color(0xFF845EEE)),
  26 + ),
  27 + );
  28 + }
  29 + return SingleChildScrollView(
  30 + padding: const EdgeInsets.symmetric(horizontal: 16),
  31 + child: Column(
  32 + children: [
  33 + const SizedBox(height: 8),
  34 + const _SleepSummaryCard(),
  35 + const SizedBox(height: 12),
  36 + const _SleepChartCard(),
  37 + const SizedBox(height: 180),
  38 + ],
  39 + ),
  40 + );
  41 + }),
  42 + ),
  43 + ],
  44 + );
  45 + }
  46 +}
  47 +
  48 +// ─── 摘要卡片 ─────────────────────────────────────────
  49 +class _SleepSummaryCard extends GetView<SleepController> {
  50 + const _SleepSummaryCard();
  51 +
  52 + static const _h1 = Color(0xFF0F0F11);
  53 + static const _h3 = Color(0xFF999999);
  54 + static const _sleepColor = Color(0xFF7B9BFB);
  55 +
  56 + @override
  57 + Widget build(BuildContext context) {
  58 + return Container(
  59 + padding: const EdgeInsets.all(20),
  60 + decoration: BoxDecoration(
  61 + color: Colors.white,
  62 + borderRadius: BorderRadius.circular(16),
  63 + ),
  64 + child: Obx(() {
  65 + final periodLabel = controller.currentPeriod.value.label;
  66 + return Column(
  67 + crossAxisAlignment: CrossAxisAlignment.start,
  68 + children: [
  69 + Row(
  70 + children: [
  71 + const Text(
  72 + '睡眠报告趋势',
  73 + style: TextStyle(
  74 + color: _h1,
  75 + fontSize: 16,
  76 + fontWeight: FontWeight.w600,
  77 + ),
  78 + ),
  79 + const SizedBox(width: 4),
  80 + const Icon(Icons.info_outline, size: 14, color: _h3),
  81 + ],
  82 + ),
  83 + const SizedBox(height: 16),
  84 + Row(
  85 + children: [
  86 + _StatItem(
  87 + label: '本$periodLabel均睡眠',
  88 + value: controller.averageSleep.value,
  89 + unit: 'h',
  90 + color: _sleepColor,
  91 + ),
  92 + const SizedBox(width: 24),
  93 + _StatItem(
  94 + label: '深睡占比',
  95 + value: controller.deepSleepRatio.value,
  96 + unit: '%',
  97 + color: const Color(0xFF845EEE),
  98 + ),
  99 + ],
  100 + ),
  101 + ],
  102 + );
  103 + }),
  104 + );
  105 + }
  106 +}
  107 +
  108 +class _StatItem extends StatelessWidget {
  109 + final String label;
  110 + final String value;
  111 + final String unit;
  112 + final Color color;
  113 +
  114 + const _StatItem({
  115 + required this.label,
  116 + required this.value,
  117 + required this.unit,
  118 + required this.color,
  119 + });
  120 +
  121 + static const _h2 = Color(0xFF78787D);
  122 +
  123 + @override
  124 + Widget build(BuildContext context) {
  125 + return Column(
  126 + crossAxisAlignment: CrossAxisAlignment.start,
  127 + children: [
  128 + Text(label, style: const TextStyle(color: _h2, fontSize: 12)),
  129 + const SizedBox(height: 4),
  130 + Row(
  131 + crossAxisAlignment: CrossAxisAlignment.end,
  132 + children: [
  133 + Text(
  134 + value,
  135 + style: TextStyle(
  136 + color: color,
  137 + fontSize: 32,
  138 + fontWeight: FontWeight.w600,
  139 + height: 1.0,
  140 + ),
  141 + ),
  142 + const SizedBox(width: 2),
  143 + Padding(
  144 + padding: const EdgeInsets.only(bottom: 4),
  145 + child: Text(unit, style: TextStyle(color: color, fontSize: 14)),
  146 + ),
  147 + ],
  148 + ),
  149 + ],
  150 + );
  151 + }
  152 +}
  153 +
  154 +// ─── 睡眠时长折线图卡片 ────────────────────────────────
  155 +class _SleepChartCard extends GetView<SleepController> {
  156 + const _SleepChartCard();
  157 +
  158 + static const _h1 = Color(0xFF0F0F11);
  159 + static const _h3 = Color(0xFF999999);
  160 + static const _h5 = Color(0xFFCCCCCC);
  161 + static const _sleepColor = Color(0xFF7B9BFB);
  162 +
  163 + @override
  164 + Widget build(BuildContext context) {
  165 + return Container(
  166 + padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
  167 + decoration: BoxDecoration(
  168 + color: Colors.white,
  169 + borderRadius: BorderRadius.circular(16),
  170 + ),
  171 + child: Obx(() {
  172 + final period = controller.currentPeriod.value;
  173 + return Column(
  174 + crossAxisAlignment: CrossAxisAlignment.start,
  175 + children: [
  176 + Text(
  177 + '睡眠时长${period.label}趋势图',
  178 + style: const TextStyle(
  179 + color: _h1,
  180 + fontSize: 14,
  181 + fontWeight: FontWeight.w600,
  182 + ),
  183 + ),
  184 + const SizedBox(height: 16),
  185 + SizedBox(
  186 + height: 180,
  187 + child: _buildChart(),
  188 + ),
  189 + ],
  190 + );
  191 + }),
  192 + );
  193 + }
  194 +
  195 + Widget _buildChart() {
  196 + if (controller.chartData.isEmpty) {
  197 + return const Center(
  198 + child: CircularProgressIndicator(
  199 + valueColor: AlwaysStoppedAnimation(Color(0xFF7B9BFB)),
  200 + ),
  201 + );
  202 + }
  203 +
  204 + return LineChart(
  205 + LineChartData(
  206 + minX: 0,
  207 + maxX: (controller.chartData.length - 1).toDouble(),
  208 + minY: 4,
  209 + maxY: 10,
  210 + gridData: FlGridData(
  211 + show: true,
  212 + drawVerticalLine: false,
  213 + horizontalInterval: 2,
  214 + getDrawingHorizontalLine: (_) => const FlLine(
  215 + color: _h5,
  216 + strokeWidth: 1,
  217 + dashArray: [2, 2],
  218 + ),
  219 + ),
  220 + borderData: FlBorderData(show: false),
  221 + titlesData: FlTitlesData(
  222 + topTitles:
  223 + const AxisTitles(sideTitles: SideTitles(showTitles: false)),
  224 + rightTitles:
  225 + const AxisTitles(sideTitles: SideTitles(showTitles: false)),
  226 + leftTitles: AxisTitles(
  227 + sideTitles: SideTitles(
  228 + showTitles: true,
  229 + reservedSize: 30,
  230 + interval: 2,
  231 + getTitlesWidget: (val, _) => Text(
  232 + '${val.toInt()}h',
  233 + style: const TextStyle(fontSize: 10, color: _h3),
  234 + ),
  235 + ),
  236 + ),
  237 + bottomTitles: AxisTitles(
  238 + sideTitles: SideTitles(
  239 + showTitles: true,
  240 + reservedSize: 20,
  241 + getTitlesWidget: (val, _) {
  242 + final i = val.toInt();
  243 + if (i < 0 || i >= controller.chartLabels.length) {
  244 + return const SizedBox.shrink();
  245 + }
  246 + return Text(
  247 + controller.chartLabels[i],
  248 + style: const TextStyle(fontSize: 10, color: _h3),
  249 + );
  250 + },
  251 + ),
  252 + ),
  253 + ),
  254 + lineBarsData: [
  255 + LineChartBarData(
  256 + spots: List.generate(
  257 + controller.chartData.length,
  258 + (i) => FlSpot(i.toDouble(), controller.chartData[i]),
  259 + ),
  260 + isCurved: true,
  261 + curveSmoothness: 0.3,
  262 + color: _sleepColor,
  263 + barWidth: 2,
  264 + belowBarData: BarAreaData(
  265 + show: true,
  266 + gradient: LinearGradient(
  267 + begin: Alignment.topCenter,
  268 + end: Alignment.bottomCenter,
  269 + colors: [
  270 + _sleepColor.withValues(alpha: 0.15),
  271 + _sleepColor.withValues(alpha: 0.0),
  272 + ],
  273 + ),
  274 + ),
  275 + dotData: FlDotData(
  276 + getDotPainter: (spot, percent, barData, index) =>
  277 + FlDotCirclePainter(
  278 + radius: 4,
  279 + color: Colors.white,
  280 + strokeWidth: 2.5,
  281 + strokeColor: _sleepColor,
  282 + ),
  283 + ),
  284 + ),
  285 + ],
  286 + lineTouchData: LineTouchData(
  287 + touchTooltipData: LineTouchTooltipData(
  288 + tooltipRoundedRadius: 8,
  289 + getTooltipColor: (_) => const Color(0xFFF3F3F3),
  290 + getTooltipItems: (spots) => spots.map((s) {
  291 + return LineTooltipItem(
  292 + '${s.y}h',
  293 + const TextStyle(
  294 + color: _sleepColor,
  295 + fontWeight: FontWeight.w600,
  296 + fontSize: 12,
  297 + ),
  298 + );
  299 + }).toList(),
  300 + ),
  301 + ),
  302 + ),
  303 + );
  304 + }
  305 +}
  1 +import 'package:flutter/material.dart';
  2 +import 'package:get/get.dart';
  3 +
  4 +import '../../controllers/trend/trend_period_controller.dart';
  5 +
  6 +/// 第3层:日期范围导航条
  7 +/// ← [5月4日 - 5月10日 ↓] →
  8 +/// 点击左右箭头切换周期;点击中间日期打开日历选择器
  9 +class TrendDateRangeBar extends StatelessWidget {
  10 + final TrendPeriodController controller;
  11 + const TrendDateRangeBar({super.key, required this.controller});
  12 +
  13 + static const _brandColor = Color(0xFF845EEE);
  14 + static const _arrowActiveColor = Color(0xFF0F0F11);
  15 + static const _arrowDisabledColor = Color(0xFFCCCCCC);
  16 +
  17 + @override
  18 + Widget build(BuildContext context) {
  19 + return Obx(
  20 + () => SizedBox(
  21 + height: 40,
  22 + child: Padding(
  23 + padding: const EdgeInsets.symmetric(horizontal: 4),
  24 + child: Row(
  25 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
  26 + children: [
  27 + // ← 上一个周期
  28 + _ArrowButton(
  29 + icon: Icons.chevron_left,
  30 + color: _arrowActiveColor,
  31 + onTap: controller.goToPrevious,
  32 + ),
  33 +
  34 + // 日期文字 + 下拉箭头(点击打开日历)
  35 + Expanded(
  36 + child: GestureDetector(
  37 + onTap: () => _showDatePicker(context),
  38 + behavior: HitTestBehavior.opaque,
  39 + child: Row(
  40 + mainAxisAlignment: MainAxisAlignment.center,
  41 + children: [
  42 + Text(
  43 + controller.dateRangeLabel,
  44 + style: const TextStyle(
  45 + fontFamily: 'PingFang SC',
  46 + fontSize: 14,
  47 + fontWeight: FontWeight.w500,
  48 + color: _brandColor,
  49 + height: 1.4,
  50 + ),
  51 + ),
  52 + const SizedBox(width: 4),
  53 + const Icon(
  54 + Icons.keyboard_arrow_down,
  55 + size: 12,
  56 + color: _brandColor,
  57 + ),
  58 + ],
  59 + ),
  60 + ),
  61 + ),
  62 +
  63 + // → 下一个周期(到达今日则禁用)
  64 + _ArrowButton(
  65 + icon: Icons.chevron_right,
  66 + color: controller.canGoNext
  67 + ? _arrowActiveColor
  68 + : _arrowDisabledColor,
  69 + onTap: controller.canGoNext ? controller.goToNext : null,
  70 + ),
  71 + ],
  72 + ),
  73 + ),
  74 + ),
  75 + );
  76 + }
  77 +
  78 + void _showDatePicker(BuildContext context) {
  79 + // TODO: 弹出自定义日历选择器
  80 + // 这里先留空,后续实现
  81 + }
  82 +}
  83 +
  84 +class _ArrowButton extends StatelessWidget {
  85 + final IconData icon;
  86 + final Color color;
  87 + final VoidCallback? onTap;
  88 +
  89 + const _ArrowButton({
  90 + required this.icon,
  91 + required this.color,
  92 + required this.onTap,
  93 + });
  94 +
  95 + @override
  96 + Widget build(BuildContext context) {
  97 + return GestureDetector(
  98 + onTap: onTap,
  99 + behavior: HitTestBehavior.opaque,
  100 + child: SizedBox(
  101 + width: 40,
  102 + height: 40,
  103 + child: Icon(icon, size: 28, color: color),
  104 + ),
  105 + );
  106 + }
  107 +}
  1 +import 'package:flutter/material.dart';
  2 +import 'package:get/get.dart';
  3 +
  4 +import '../../controllers/trend/trend_types.dart';
  5 +import '../../controllers/trend/trend_period_controller.dart';
  6 +
  7 +/// 第2层:周 / 月 / 年 胶囊 Tab
  8 +/// 设计:整体 rgba(132,94,238,0.2) 胶囊背景,选中项白色圆角块
  9 +/// 仅支持点击切换(不滑动,避免与第1层手势冲突)
  10 +class TrendPeriodTabBar extends StatelessWidget {
  11 + final TrendPeriodController controller;
  12 + const TrendPeriodTabBar({super.key, required this.controller});
  13 +
  14 + // Figma token
  15 + static const _capsuleBg = Color(0x33845EEE); // rgba(132,94,238,0.2)
  16 + static const _selectedBg = Colors.white;
  17 + static const _h1 = Color(0xFF0F0F11);
  18 + static const _h2 = Color(0xFF78787D);
  19 +
  20 + @override
  21 + Widget build(BuildContext context) {
  22 + return Padding(
  23 + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
  24 + child: Obx(
  25 + () => Container(
  26 + height: 36,
  27 + decoration: BoxDecoration(
  28 + color: _capsuleBg,
  29 + borderRadius: BorderRadius.circular(25),
  30 + ),
  31 + padding: const EdgeInsets.all(4),
  32 + child: Row(
  33 + children: TrendPeriod.values.map((period) {
  34 + final isSelected = controller.currentPeriod.value == period;
  35 + return Expanded(
  36 + child: GestureDetector(
  37 + onTap: () => controller.changePeriod(period),
  38 + behavior: HitTestBehavior.opaque,
  39 + child: AnimatedContainer(
  40 + duration: const Duration(milliseconds: 200),
  41 + curve: Curves.easeInOut,
  42 + decoration: BoxDecoration(
  43 + color: isSelected ? _selectedBg : Colors.transparent,
  44 + borderRadius: BorderRadius.circular(21),
  45 + boxShadow: isSelected
  46 + ? [
  47 + const BoxShadow(
  48 + color: Color(0x14000000),
  49 + blurRadius: 4,
  50 + offset: Offset(0, 1),
  51 + )
  52 + ]
  53 + : null,
  54 + ),
  55 + alignment: Alignment.center,
  56 + child: AnimatedDefaultTextStyle(
  57 + duration: const Duration(milliseconds: 200),
  58 + style: TextStyle(
  59 + fontFamily: 'PingFang SC',
  60 + fontSize: 14,
  61 + fontWeight: FontWeight.w500,
  62 + color: isSelected ? _h1 : _h2,
  63 + height: 1.4,
  64 + ),
  65 + child: Text(period.label),
  66 + ),
  67 + ),
  68 + ),
  69 + );
  70 + }).toList(),
  71 + ),
  72 + ),
  73 + ),
  74 + );
  75 + }
  76 +}
  1 +import 'package:flutter/material.dart';
  2 +
  3 +/// 第1层:HRV心率 / 活动消耗 / 睡眠报告 Tab
  4 +/// 使用 Flutter 原生 TabBar 与 TabController 实现极其平滑、跟手的指示线滑动效果
  5 +class TrendTypeTabBar extends StatelessWidget {
  6 + final TabController tabController;
  7 + const TrendTypeTabBar({super.key, required this.tabController});
  8 +
  9 + static const _selectedColor = Color(0xFF0F0F11);
  10 + static const _unselectedColor = Color(0xFF78787D);
  11 +
  12 + @override
  13 + Widget build(BuildContext context) {
  14 + return SizedBox(
  15 + height: 40,
  16 + child: TabBar(
  17 + controller: tabController,
  18 + dividerColor: Colors.transparent, // 移除底部默认分割线
  19 + indicatorColor: _selectedColor,
  20 + indicatorSize: TabBarIndicatorSize.tab, // 指示线与 Tab 等宽
  21 + indicatorWeight: 3, // 指示线粗细为 3px
  22 + labelColor: _selectedColor,
  23 + unselectedLabelColor: _unselectedColor,
  24 + labelStyle: const TextStyle(
  25 + fontFamily: 'PingFang SC',
  26 + fontSize: 18,
  27 + fontWeight: FontWeight.w600,
  28 + ),
  29 + unselectedLabelStyle: const TextStyle(
  30 + fontFamily: 'PingFang SC',
  31 + fontSize: 18,
  32 + fontWeight: FontWeight.w500,
  33 + ),
  34 + tabs: const [
  35 + Tab(text: 'HRV心率'),
  36 + Tab(text: '活动消耗'),
  37 + Tab(text: '睡眠报告'),
  38 + ],
  39 + ),
  40 + );
  41 + }
  42 +}
  1 +import 'package:flutter/material.dart';
  2 +
  3 +/// Raw hex color constants directly from Double Feel Figma Design System.
  4 +/// Avoid using these directly in UI. Instead, use `context.colors` to access
  5 +/// semantic colors that automatically adjust to light/dark themes.
  6 +class AppColors {
  7 + AppColors._();
  8 +
  9 + // ==========================================
  10 + // 品牌色 (Brand Colors)
  11 + // ==========================================
  12 + static const primary = Color(0xFF845EEE);
  13 +
  14 + // ==========================================
  15 + // 字色与分割线 (Text & Dividers)
  16 + // ==========================================
  17 + static const textPrimary = Color(0xFF0F0F11);
  18 + static const textSecondary = Color(0xFF78787D);
  19 + static const textTertiary = Color(0xFFB0B0B6);
  20 + static const disabled = Color(0xFFD0D0D2);
  21 + static const border = Color(0xFFEBEBED);
  22 +
  23 + // ==========================================
  24 + // 图表与功能色 (Chart & Features)
  25 + // ==========================================
  26 + static const chartPink = Color(0xFFFF5279);
  27 + static const chartOrange = Color(0xFFFF9A6E);
  28 + static const chartBlue = Color(0xFF7B9AFB);
  29 + static const chartGreen = Color(0xFF3BD49D);
  30 + static const chartPurple = Color(0xFFA084EF);
  31 + static const chartMagenta = Color(0xFFF775E1);
  32 +
  33 + // ==========================================
  34 + // 会员色 (VIP Colors)
  35 + // ==========================================
  36 + static const vipText = Color(0xFF0F0F11);
  37 + static const vipGold = Color(0xFFFFCE51);
  38 + static const vipPurple = Color(0xFF916DF5);
  39 +
  40 + // ==========================================
  41 + // 辅助与背景色 (Auxiliary & Backgrounds)
  42 + // ==========================================
  43 + static const backgroundLight = Color(0xFFFAFAFE);
  44 + static const brandBackgroundLight = Color(0xFFEAE3FF);
  45 +
  46 + // ==========================================
  47 + // 渐变基础色 (Gradient Components)
  48 + // ==========================================
  49 + static const gradientStart = Color(0xFFECE3FF);
  50 + static const gradientEnd = Color(0xFFF9F6FF);
  51 +}
  1 +import 'package:flutter/material.dart';
  2 +import 'app_colors.dart';
  3 +
  4 +/// Semantic colors extension for Double Feel application.
  5 +/// Exposes structured colors and gradients supporting Light and Dark modes.
  6 +class AppColorsExtension extends ThemeExtension<AppColorsExtension> {
  7 + // Brand Colors
  8 + final Color primary;
  9 +
  10 + // Text & Border Colors
  11 + final Color textPrimary;
  12 + final Color textSecondary;
  13 + final Color textTertiary;
  14 + final Color disabled;
  15 + final Color border;
  16 +
  17 + // Chart & Features Colors
  18 + final Color chartPink;
  19 + final Color chartOrange;
  20 + final Color chartBlue;
  21 + final Color chartGreen;
  22 + final Color chartPurple;
  23 + final Color chartMagenta;
  24 +
  25 + // VIP Colors
  26 + final Color vipText;
  27 + final Color vipGold;
  28 + final Color vipPurple;
  29 +
  30 + // Auxiliary Colors
  31 + final Color backgroundLight;
  32 + final Color brandBackgroundLight;
  33 +
  34 + // Gradients
  35 + final LinearGradient brandBackgroundGradient;
  36 +
  37 + const AppColorsExtension({
  38 + required this.primary,
  39 + required this.textPrimary,
  40 + required this.textSecondary,
  41 + required this.textTertiary,
  42 + required this.disabled,
  43 + required this.border,
  44 + required this.chartPink,
  45 + required this.chartOrange,
  46 + required this.chartBlue,
  47 + required this.chartGreen,
  48 + required this.chartPurple,
  49 + required this.chartMagenta,
  50 + required this.vipText,
  51 + required this.vipGold,
  52 + required this.vipPurple,
  53 + required this.backgroundLight,
  54 + required this.brandBackgroundLight,
  55 + required this.brandBackgroundGradient,
  56 + });
  57 +
  58 + /// The standard light palette derived directly from Figma.
  59 + factory AppColorsExtension.light() {
  60 + return const AppColorsExtension(
  61 + primary: AppColors.primary,
  62 + textPrimary: AppColors.textPrimary,
  63 + textSecondary: AppColors.textSecondary,
  64 + textTertiary: AppColors.textTertiary,
  65 + disabled: AppColors.disabled,
  66 + border: AppColors.border,
  67 + chartPink: AppColors.chartPink,
  68 + chartOrange: AppColors.chartOrange,
  69 + chartBlue: AppColors.chartBlue,
  70 + chartGreen: AppColors.chartGreen,
  71 + chartPurple: AppColors.chartPurple,
  72 + chartMagenta: AppColors.chartMagenta,
  73 + vipText: AppColors.vipText,
  74 + vipGold: AppColors.vipGold,
  75 + vipPurple: AppColors.vipPurple,
  76 + backgroundLight: AppColors.backgroundLight,
  77 + brandBackgroundLight: AppColors.brandBackgroundLight,
  78 + brandBackgroundGradient: LinearGradient(
  79 + colors: [AppColors.gradientStart, AppColors.gradientEnd],
  80 + begin: Alignment.centerLeft,
  81 + end: Alignment.centerRight,
  82 + ),
  83 + );
  84 + }
  85 +
  86 + /// Initial dark mode mapping. Semantic mappings adapt nicely when Dark mode is toggled.
  87 + factory AppColorsExtension.dark() {
  88 + return const AppColorsExtension(
  89 + primary: AppColors.primary,
  90 + textPrimary: Color(0xFFEEEEEE),
  91 + textSecondary: Color(0xFFAAAAAA),
  92 + textTertiary: Color(0xFF666666),
  93 + disabled: Color(0xFF444444),
  94 + border: Color(0xFF333333),
  95 + chartPink: AppColors.chartPink,
  96 + chartOrange: AppColors.chartOrange,
  97 + chartBlue: AppColors.chartBlue,
  98 + chartGreen: AppColors.chartGreen,
  99 + chartPurple: AppColors.chartPurple,
  100 + chartMagenta: AppColors.chartMagenta,
  101 + vipText: Color(0xFFEEEEEE),
  102 + vipGold: AppColors.vipGold,
  103 + vipPurple: AppColors.vipPurple,
  104 + backgroundLight: Color(0xFF121214),
  105 + brandBackgroundLight: Color(0xFF241C35),
  106 + brandBackgroundGradient: LinearGradient(
  107 + colors: [Color(0xFF241C35), Color(0xFF1A1525)],
  108 + begin: Alignment.centerLeft,
  109 + end: Alignment.centerRight,
  110 + ),
  111 + );
  112 + }
  113 +
  114 + @override
  115 + AppColorsExtension copyWith({
  116 + Color? primary,
  117 + Color? textPrimary,
  118 + Color? textSecondary,
  119 + Color? textTertiary,
  120 + Color? disabled,
  121 + Color? border,
  122 + Color? chartPink,
  123 + Color? chartOrange,
  124 + Color? chartBlue,
  125 + Color? chartGreen,
  126 + Color? chartPurple,
  127 + Color? chartMagenta,
  128 + Color? vipText,
  129 + Color? vipGold,
  130 + Color? vipPurple,
  131 + Color? backgroundLight,
  132 + Color? brandBackgroundLight,
  133 + LinearGradient? brandBackgroundGradient,
  134 + }) {
  135 + return AppColorsExtension(
  136 + primary: primary ?? this.primary,
  137 + textPrimary: textPrimary ?? this.textPrimary,
  138 + textSecondary: textSecondary ?? this.textSecondary,
  139 + textTertiary: textTertiary ?? this.textTertiary,
  140 + disabled: disabled ?? this.disabled,
  141 + border: border ?? this.border,
  142 + chartPink: chartPink ?? this.chartPink,
  143 + chartOrange: chartOrange ?? this.chartOrange,
  144 + chartBlue: chartBlue ?? this.chartBlue,
  145 + chartGreen: chartGreen ?? this.chartGreen,
  146 + chartPurple: chartPurple ?? this.chartPurple,
  147 + chartMagenta: chartMagenta ?? this.chartMagenta,
  148 + vipText: vipText ?? this.vipText,
  149 + vipGold: vipGold ?? this.vipGold,
  150 + vipPurple: vipPurple ?? this.vipPurple,
  151 + backgroundLight: backgroundLight ?? this.backgroundLight,
  152 + brandBackgroundLight: brandBackgroundLight ?? this.brandBackgroundLight,
  153 + brandBackgroundGradient: brandBackgroundGradient ?? this.brandBackgroundGradient,
  154 + );
  155 + }
  156 +
  157 + @override
  158 + AppColorsExtension lerp(ThemeExtension<AppColorsExtension>? other, double t) {
  159 + if (other is! AppColorsExtension) {
  160 + return this;
  161 + }
  162 + return AppColorsExtension(
  163 + primary: Color.lerp(primary, other.primary, t)!,
  164 + textPrimary: Color.lerp(textPrimary, other.textPrimary, t)!,
  165 + textSecondary: Color.lerp(textSecondary, other.textSecondary, t)!,
  166 + textTertiary: Color.lerp(textTertiary, other.textTertiary, t)!,
  167 + disabled: Color.lerp(disabled, other.disabled, t)!,
  168 + border: Color.lerp(border, other.border, t)!,
  169 + chartPink: Color.lerp(chartPink, other.chartPink, t)!,
  170 + chartOrange: Color.lerp(chartOrange, other.chartOrange, t)!,
  171 + chartBlue: Color.lerp(chartBlue, other.chartBlue, t)!,
  172 + chartGreen: Color.lerp(chartGreen, other.chartGreen, t)!,
  173 + chartPurple: Color.lerp(chartPurple, other.chartPurple, t)!,
  174 + chartMagenta: Color.lerp(chartMagenta, other.chartMagenta, t)!,
  175 + vipText: Color.lerp(vipText, other.vipText, t)!,
  176 + vipGold: Color.lerp(vipGold, other.vipGold, t)!,
  177 + vipPurple: Color.lerp(vipPurple, other.vipPurple, t)!,
  178 + backgroundLight: Color.lerp(backgroundLight, other.backgroundLight, t)!,
  179 + brandBackgroundLight: Color.lerp(brandBackgroundLight, other.brandBackgroundLight, t)!,
  180 + brandBackgroundGradient: LinearGradient.lerp(brandBackgroundGradient, other.brandBackgroundGradient, t)!,
  181 + );
  182 + }
  183 +}
  1 +import 'package:flutter/material.dart';
  2 +import 'app_colors_extension.dart';
  3 +
  4 +/// Class managing the Application ThemeData for both Light and Dark themes.
  5 +/// Automatically hooks up our custom Figma colors system as a ThemeExtension.
  6 +class AppTheme {
  7 + AppTheme._();
  8 +
  9 + /// The standard light theme configuration.
  10 + static ThemeData get lightTheme {
  11 + final colors = AppColorsExtension.light();
  12 + return ThemeData(
  13 + useMaterial3: true,
  14 + brightness: Brightness.light,
  15 + primaryColor: colors.primary,
  16 + scaffoldBackgroundColor: colors.backgroundLight,
  17 +
  18 + // Clean modern AppBar theme using Figma colors
  19 + appBarTheme: AppBarTheme(
  20 + backgroundColor: colors.backgroundLight,
  21 + elevation: 0,
  22 + centerTitle: true,
  23 + iconTheme: IconThemeData(color: colors.textPrimary),
  24 + actionsIconTheme: IconThemeData(color: colors.textPrimary),
  25 + titleTextStyle: TextStyle(
  26 + color: colors.textPrimary,
  27 + fontSize: 18,
  28 + fontWeight: FontWeight.w600,
  29 + ),
  30 + ),
  31 +
  32 + // Configure default ColorScheme using Figma specs
  33 + colorScheme: ColorScheme.light(
  34 + primary: colors.primary,
  35 + secondary: colors.primary,
  36 + surface: colors.backgroundLight,
  37 + onPrimary: Colors.white,
  38 + onSecondary: Colors.white,
  39 + onSurface: colors.textPrimary,
  40 + outline: colors.border,
  41 + ),
  42 +
  43 + // Register the custom theme extension
  44 + extensions: [
  45 + colors,
  46 + ],
  47 + );
  48 + }
  49 +
  50 + /// Initial dark theme configuration.
  51 + static ThemeData get darkTheme {
  52 + final colors = AppColorsExtension.dark();
  53 + return ThemeData(
  54 + useMaterial3: true,
  55 + brightness: Brightness.dark,
  56 + primaryColor: colors.primary,
  57 + scaffoldBackgroundColor: colors.backgroundLight,
  58 +
  59 + appBarTheme: AppBarTheme(
  60 + backgroundColor: colors.backgroundLight,
  61 + elevation: 0,
  62 + centerTitle: true,
  63 + iconTheme: IconThemeData(color: colors.textPrimary),
  64 + actionsIconTheme: IconThemeData(color: colors.textPrimary),
  65 + titleTextStyle: TextStyle(
  66 + color: colors.textPrimary,
  67 + fontSize: 18,
  68 + fontWeight: FontWeight.w600,
  69 + ),
  70 + ),
  71 +
  72 + colorScheme: ColorScheme.dark(
  73 + primary: colors.primary,
  74 + secondary: colors.primary,
  75 + surface: colors.backgroundLight,
  76 + onPrimary: Colors.white,
  77 + onSecondary: Colors.white,
  78 + onSurface: colors.textPrimary,
  79 + outline: colors.border,
  80 + ),
  81 +
  82 + extensions: [
  83 + colors,
  84 + ],
  85 + );
  86 + }
  87 +}
  88 +
  89 +/// Helper extension to easily access custom Figma colors inside widgets
  90 +/// by using `context.colors.<semanticName>` instead of verbose lookups.
  91 +extension AppThemeContextExtension on BuildContext {
  92 + AppColorsExtension get colors => Theme.of(this).extension<AppColorsExtension>()!;
  93 + ThemeData get theme => Theme.of(this);
  94 +}
@@ -62,8 +62,7 @@ import 'app_localizations_zh.dart'; @@ -62,8 +62,7 @@ import 'app_localizations_zh.dart';
62 /// be consistent with the languages listed in the AppLocalizations.supportedLocales 62 /// be consistent with the languages listed in the AppLocalizations.supportedLocales
63 /// property. 63 /// property.
64 abstract class AppLocalizations { 64 abstract class AppLocalizations {
65 - AppLocalizations(String locale)  
66 - : localeName = intl.Intl.canonicalizedLocale(locale.toString()); 65 + AppLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString());
67 66
68 final String localeName; 67 final String localeName;
69 68
@@ -71,8 +70,7 @@ abstract class AppLocalizations { @@ -71,8 +70,7 @@ abstract class AppLocalizations {
71 return Localizations.of<AppLocalizations>(context, AppLocalizations); 70 return Localizations.of<AppLocalizations>(context, AppLocalizations);
72 } 71 }
73 72
74 - static const LocalizationsDelegate<AppLocalizations> delegate =  
75 - _AppLocalizationsDelegate(); 73 + static const LocalizationsDelegate<AppLocalizations> delegate = _AppLocalizationsDelegate();
76 74
77 /// A list of this localizations delegate along with the default localizations 75 /// A list of this localizations delegate along with the default localizations
78 /// delegates. 76 /// delegates.
@@ -84,8 +82,7 @@ abstract class AppLocalizations { @@ -84,8 +82,7 @@ abstract class AppLocalizations {
84 /// Additional delegates can be added by appending to this list in 82 /// Additional delegates can be added by appending to this list in
85 /// MaterialApp. This list does not have to be used at all if a custom list 83 /// MaterialApp. This list does not have to be used at all if a custom list
86 /// of delegates is preferred or required. 84 /// of delegates is preferred or required.
87 - static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =  
88 - <LocalizationsDelegate<dynamic>>[ 85 + static const List<LocalizationsDelegate<dynamic>> localizationsDelegates = <LocalizationsDelegate<dynamic>>[
89 delegate, 86 delegate,
90 GlobalMaterialLocalizations.delegate, 87 GlobalMaterialLocalizations.delegate,
91 GlobalCupertinoLocalizations.delegate, 88 GlobalCupertinoLocalizations.delegate,
@@ -528,8 +525,7 @@ abstract class AppLocalizations { @@ -528,8 +525,7 @@ abstract class AppLocalizations {
528 /// 525 ///
529 /// In zh, this message translates to: 526 /// In zh, this message translates to:
530 /// **'你们现在可以查看彼此的 HRV、压力与睡眠变化,并在对方疲惫时及时送上关心。'** 527 /// **'你们现在可以查看彼此的 HRV、压力与睡眠变化,并在对方疲惫时及时送上关心。'**
531 - String  
532 - get youCanNowViewEachOtherSHrvStressLevelsAndSleepPatternsAndReachOutToCheckInWhenTheOtherPersonSeemsTired; 528 + String get youCanNowViewEachOtherSHrvStressLevelsAndSleepPatternsAndReachOutToCheckInWhenTheOtherPersonSeemsTired;
533 529
534 /// No description provided for @bindPartnerTitle. 530 /// No description provided for @bindPartnerTitle.
535 /// 531 ///
@@ -610,8 +606,7 @@ abstract class AppLocalizations { @@ -610,8 +606,7 @@ abstract class AppLocalizations {
610 String get onboardingResearchGoodSleep; 606 String get onboardingResearchGoodSleep;
611 } 607 }
612 608
613 -class _AppLocalizationsDelegate  
614 - extends LocalizationsDelegate<AppLocalizations> { 609 +class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
615 const _AppLocalizationsDelegate(); 610 const _AppLocalizationsDelegate();
616 611
617 @override 612 @override
@@ -620,25 +615,25 @@ class _AppLocalizationsDelegate @@ -620,25 +615,25 @@ class _AppLocalizationsDelegate
620 } 615 }
621 616
622 @override 617 @override
623 - bool isSupported(Locale locale) =>  
624 - <String>['en', 'zh'].contains(locale.languageCode); 618 + bool isSupported(Locale locale) => <String>['en', 'zh'].contains(locale.languageCode);
625 619
626 @override 620 @override
627 bool shouldReload(_AppLocalizationsDelegate old) => false; 621 bool shouldReload(_AppLocalizationsDelegate old) => false;
628 } 622 }
629 623
630 AppLocalizations lookupAppLocalizations(Locale locale) { 624 AppLocalizations lookupAppLocalizations(Locale locale) {
  625 +
  626 +
631 // Lookup logic when only language code is specified. 627 // Lookup logic when only language code is specified.
632 switch (locale.languageCode) { 628 switch (locale.languageCode) {
633 - case 'en':  
634 - return AppLocalizationsEn();  
635 - case 'zh':  
636 - return AppLocalizationsZh(); 629 + case 'en': return AppLocalizationsEn();
  630 + case 'zh': return AppLocalizationsZh();
637 } 631 }
638 632
639 throw FlutterError( 633 throw FlutterError(
640 - 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '  
641 - 'an issue with the localizations generation tool. Please file an issue '  
642 - 'on GitHub with a reproducible sample app and the gen-l10n configuration '  
643 - 'that was used.'); 634 + 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
  635 + 'an issue with the localizations generation tool. Please file an issue '
  636 + 'on GitHub with a reproducible sample app and the gen-l10n configuration '
  637 + 'that was used.'
  638 + );
644 } 639 }
1 -// ignore: unused_import  
2 -import 'package:intl/intl.dart' as intl;  
3 import 'app_localizations.dart'; 1 import 'app_localizations.dart';
4 2
5 // ignore_for_file: type=lint 3 // ignore_for_file: type=lint
@@ -69,12 +67,10 @@ class AppLocalizationsEn extends AppLocalizations { @@ -69,12 +67,10 @@ class AppLocalizationsEn extends AppLocalizations {
69 String get settings => 'Settings'; 67 String get settings => 'Settings';
70 68
71 @override 69 @override
72 - String get onboardingIntroTitle =>  
73 - 'DoubleFeel is a health companion app built for Apple Watch'; 70 + String get onboardingIntroTitle => 'DoubleFeel is a health companion app built for Apple Watch';
74 71
75 @override 72 @override
76 - String get onboardingIntroBody =>  
77 - 'We hope to help you\n<em>notice changes in your mind and body, and help the people who love you</em> see when you are <em>tired or need support</em>'; 73 + String get onboardingIntroBody => 'We hope to help you\n<em>notice changes in your mind and body, and help the people who love you</em> see when you are <em>tired or need support</em>';
78 74
79 @override 75 @override
80 String get onboardingStateQuestion => 'Which of these often happens to you?'; 76 String get onboardingStateQuestion => 'Which of these often happens to you?';
@@ -86,19 +82,16 @@ class AppLocalizationsEn extends AppLocalizations { @@ -86,19 +82,16 @@ class AppLocalizationsEn extends AppLocalizations {
86 String get onboardingStateTired => 'I get tired easily'; 82 String get onboardingStateTired => 'I get tired easily';
87 83
88 @override 84 @override
89 - String get onboardingStatePoorRest =>  
90 - 'I wake up but still do not feel rested'; 85 + String get onboardingStatePoorRest => 'I wake up but still do not feel rested';
91 86
92 @override 87 @override
93 - String get onboardingStateNeedStimulants =>  
94 - 'I rely on cigarettes, alcohol, coffee, or other stimulants to stay alert'; 88 + String get onboardingStateNeedStimulants => 'I rely on cigarettes, alcohol, coffee, or other stimulants to stay alert';
95 89
96 @override 90 @override
97 String get onboardingStateNone => 'None of the above'; 91 String get onboardingStateNone => 'None of the above';
98 92
99 @override 93 @override
100 - String get onboardingStressGoalQuestion =>  
101 - 'What do you want to learn by understanding stress?'; 94 + String get onboardingStressGoalQuestion => 'What do you want to learn by understanding stress?';
102 95
103 @override 96 @override
104 String get onboardingStressGoalSource => 'Understand where stress comes from'; 97 String get onboardingStressGoalSource => 'Understand where stress comes from';
@@ -107,8 +100,7 @@ class AppLocalizationsEn extends AppLocalizations { @@ -107,8 +100,7 @@ class AppLocalizationsEn extends AppLocalizations {
107 String get onboardingStressGoalReminder => 'Get reminded when stress appears'; 100 String get onboardingStressGoalReminder => 'Get reminded when stress appears';
108 101
109 @override 102 @override
110 - String get onboardingStressGoalLovedOnes =>  
111 - 'Let people who care about me know my stress state'; 103 + String get onboardingStressGoalLovedOnes => 'Let people who care about me know my stress state';
112 104
113 @override 105 @override
114 String get onboardingStressGoalRelax => 'Understand stress and feel lighter'; 106 String get onboardingStressGoalRelax => 'Understand stress and feel lighter';
@@ -117,8 +109,7 @@ class AppLocalizationsEn extends AppLocalizations { @@ -117,8 +109,7 @@ class AppLocalizationsEn extends AppLocalizations {
117 String get onboardingStressGoalBodyTalk => 'Communicate better with my body'; 109 String get onboardingStressGoalBodyTalk => 'Communicate better with my body';
118 110
119 @override 111 @override
120 - String get onboardingReliefQuestion =>  
121 - 'Which methods do you think can ease stress?'; 112 + String get onboardingReliefQuestion => 'Which methods do you think can ease stress?';
122 113
123 @override 114 @override
124 String get onboardingReliefSleep => 'Regular sleep'; 115 String get onboardingReliefSleep => 'Regular sleep';
@@ -142,8 +133,7 @@ class AppLocalizationsEn extends AppLocalizations { @@ -142,8 +133,7 @@ class AppLocalizationsEn extends AppLocalizations {
142 String get onboardingKeyDataTitle => 'Did you know?'; 133 String get onboardingKeyDataTitle => 'Did you know?';
143 134
144 @override 135 @override
145 - String get onboardingKeyDataSubtitle =>  
146 - 'Everyone has a magical and important body metric that can help us:'; 136 + String get onboardingKeyDataSubtitle => 'Everyone has a magical and important body metric that can help us:';
147 137
148 @override 138 @override
149 String get onboardingKeyDataStress => 'Monitor stress'; 139 String get onboardingKeyDataStress => 'Monitor stress';
@@ -158,8 +148,7 @@ class AppLocalizationsEn extends AppLocalizations { @@ -158,8 +148,7 @@ class AppLocalizationsEn extends AppLocalizations {
158 String get onboardingKeyDataHabits => 'Build healthy habits'; 148 String get onboardingKeyDataHabits => 'Build healthy habits';
159 149
160 @override 150 @override
161 - String get onboardingKeyDataLovedOnes =>  
162 - 'Help important people care about your state in time'; 151 + String get onboardingKeyDataLovedOnes => 'Help important people care about your state in time';
163 152
164 @override 153 @override
165 String get onboardingTellMeWhatItIs => 'Tell me what it is!'; 154 String get onboardingTellMeWhatItIs => 'Tell me what it is!';
@@ -168,19 +157,16 @@ class AppLocalizationsEn extends AppLocalizations { @@ -168,19 +157,16 @@ class AppLocalizationsEn extends AppLocalizations {
168 String get onboardingHrvTitle => 'It is HRV, heart rate variability'; 157 String get onboardingHrvTitle => 'It is HRV, heart rate variability';
169 158
170 @override 159 @override
171 - String get onboardingHrvSubtitle =>  
172 - 'It helps us measure overall stress and health'; 160 + String get onboardingHrvSubtitle => 'It helps us measure overall stress and health';
173 161
174 @override 162 @override
175 - String get onboardingHrvDescription =>  
176 - 'Heart rate variability (HRV) is the tiny variation in time between heartbeats. It reflects autonomic nervous system activity and how the body responds to stress.'; 163 + String get onboardingHrvDescription => 'Heart rate variability (HRV) is the tiny variation in time between heartbeats. It reflects autonomic nervous system activity and how the body responds to stress.';
177 164
178 @override 165 @override
179 String get onboardingTellMeMore => 'Tell me more'; 166 String get onboardingTellMeMore => 'Tell me more';
180 167
181 @override 168 @override
182 - String get onboardingResearchTitle =>  
183 - 'Many studies show that HRV changes are closely related to how our body and mind feel'; 169 + String get onboardingResearchTitle => 'Many studies show that HRV changes are closely related to how our body and mind feel';
184 170
185 @override 171 @override
186 String get onboardingResearchFatigue => 'Physical fatigue'; 172 String get onboardingResearchFatigue => 'Physical fatigue';
@@ -198,30 +184,25 @@ class AppLocalizationsEn extends AppLocalizations { @@ -198,30 +184,25 @@ class AppLocalizationsEn extends AppLocalizations {
198 String get onboardingHealthPermissionTitle => 'Allow health data access'; 184 String get onboardingHealthPermissionTitle => 'Allow health data access';
199 185
200 @override 186 @override
201 - String get onboardingHealthPermissionBody =>  
202 - 'DoubleFeel needs connected wearable health data to send reminders, count stress moments, and provide suggestions.'; 187 + String get onboardingHealthPermissionBody => 'DoubleFeel needs connected wearable health data to send reminders, count stress moments, and provide suggestions.';
203 188
204 @override 189 @override
205 - String get onboardingHealthPermissionPrivacy =>  
206 - 'Your health data is stored locally. We do not upload any related data.'; 190 + String get onboardingHealthPermissionPrivacy => 'Your health data is stored locally. We do not upload any related data.';
207 191
208 @override 192 @override
209 String get onboardingNotificationTitle => 'Turn on notifications'; 193 String get onboardingNotificationTitle => 'Turn on notifications';
210 194
211 @override 195 @override
212 - String get onboardingNotificationSubtitle =>  
213 - 'Learn about every body change in time'; 196 + String get onboardingNotificationSubtitle => 'Learn about every body change in time';
214 197
215 @override 198 @override
216 - String get onboardingNotificationBody =>  
217 - 'After Apple Watch data updates, we can remind you in time and help you act to improve your stress state.'; 199 + String get onboardingNotificationBody => 'After Apple Watch data updates, we can remind you in time and help you act to improve your stress state.';
218 200
219 @override 201 @override
220 String get onboardingMemberTitle => 'Get an annual membership offer'; 202 String get onboardingMemberTitle => 'Get an annual membership offer';
221 203
222 @override 204 @override
223 - String get onboardingMemberBody =>  
224 - 'Start your pressure alert and health companion journey, so love and care are always present.'; 205 + String get onboardingMemberBody => 'Start your pressure alert and health companion journey, so love and care are always present.';
225 206
226 @override 207 @override
227 String get onboardingMemberOriginalPrice => 'Original ¥72.00/year'; 208 String get onboardingMemberOriginalPrice => 'Original ¥72.00/year';
@@ -236,16 +217,13 @@ class AppLocalizationsEn extends AppLocalizations { @@ -236,16 +217,13 @@ class AppLocalizationsEn extends AppLocalizations {
236 String get onboardingMemberAllOptions => 'View all purchase options'; 217 String get onboardingMemberAllOptions => 'View all purchase options';
237 218
238 @override 219 @override
239 - String get healthCompanionIsNowAvailable =>  
240 - 'Health Companion is now available'; 220 + String get healthCompanionIsNowAvailable => 'Health Companion is now available';
241 221
242 @override 222 @override
243 - String get youCanNowViewEachOtherSHrvStressLevelsAndSleepPatternsAndReachOutToCheckInWhenTheOtherPersonSeemsTired =>  
244 - 'You can now view each other\'s HRV, stress levels, and sleep patterns, and reach out to check in when the other person seems tired.'; 223 + String get youCanNowViewEachOtherSHrvStressLevelsAndSleepPatternsAndReachOutToCheckInWhenTheOtherPersonSeemsTired => 'You can now view each other\'s HRV, stress levels, and sleep patterns, and reach out to check in when the other person seems tired.';
245 224
246 @override 225 @override
247 - String get bindPartnerTitle =>  
248 - 'Add a Close Contact\nOne more person to care about your health'; 226 + String get bindPartnerTitle => 'Add a Close Contact\nOne more person to care about your health';
249 227
250 @override 228 @override
251 String get bindPartnerMyId => 'My ID'; 229 String get bindPartnerMyId => 'My ID';
1 -// ignore: unused_import  
2 -import 'package:intl/intl.dart' as intl;  
3 import 'app_localizations.dart'; 1 import 'app_localizations.dart';
4 2
5 // ignore_for_file: type=lint 3 // ignore_for_file: type=lint
@@ -72,8 +70,7 @@ class AppLocalizationsZh extends AppLocalizations { @@ -72,8 +70,7 @@ class AppLocalizationsZh extends AppLocalizations {
72 String get onboardingIntroTitle => 'DoubleFeel 是专为 Apple Watch 打造的健康陪伴app'; 70 String get onboardingIntroTitle => 'DoubleFeel 是专为 Apple Watch 打造的健康陪伴app';
73 71
74 @override 72 @override
75 - String get onboardingIntroBody =>  
76 - '我们希望可以帮助你\n<em>关注自己的身心变化,也让爱你的人</em>及时发现你的<em>疲惫与需要</em>'; 73 + String get onboardingIntroBody => '我们希望可以帮助你\n<em>关注自己的身心变化,也让爱你的人</em>及时发现你的<em>疲惫与需要</em>';
77 74
78 @override 75 @override
79 String get onboardingStateQuestion => '请问以下哪些描述,经常发生在你身上?'; 76 String get onboardingStateQuestion => '请问以下哪些描述,经常发生在你身上?';
@@ -163,8 +160,7 @@ class AppLocalizationsZh extends AppLocalizations { @@ -163,8 +160,7 @@ class AppLocalizationsZh extends AppLocalizations {
163 String get onboardingHrvSubtitle => '它能帮助我们衡量整体的压力和健康状态'; 160 String get onboardingHrvSubtitle => '它能帮助我们衡量整体的压力和健康状态';
164 161
165 @override 162 @override
166 - String get onboardingHrvDescription =>  
167 - '心率变异性(HRV, Heart Rate Variability)即心跳之间间隔时间的微小变化,反映了自主神经系统活动和身体对压力的反应能力'; 163 + String get onboardingHrvDescription => '心率变异性(HRV, Heart Rate Variability)即心跳之间间隔时间的微小变化,反映了自主神经系统活动和身体对压力的反应能力';
168 164
169 @override 165 @override
170 String get onboardingTellMeMore => '展开说说'; 166 String get onboardingTellMeMore => '展开说说';
@@ -188,12 +184,10 @@ class AppLocalizationsZh extends AppLocalizations { @@ -188,12 +184,10 @@ class AppLocalizationsZh extends AppLocalizations {
188 String get onboardingHealthPermissionTitle => '允许访问健康数据'; 184 String get onboardingHealthPermissionTitle => '允许访问健康数据';
189 185
190 @override 186 @override
191 - String get onboardingHealthPermissionBody =>  
192 - 'DoubleFeel需要连接健康穿戴设备数据,以提醒、统计压力时刻、提供建议。'; 187 + String get onboardingHealthPermissionBody => 'DoubleFeel需要连接健康穿戴设备数据,以提醒、统计压力时刻、提供建议。';
193 188
194 @override 189 @override
195 - String get onboardingHealthPermissionPrivacy =>  
196 - '请放心,你的健康数据只会存储在本地,我们不上传任何相关数据。'; 190 + String get onboardingHealthPermissionPrivacy => '请放心,你的健康数据只会存储在本地,我们不上传任何相关数据。';
197 191
198 @override 192 @override
199 String get onboardingNotificationTitle => '开启通知'; 193 String get onboardingNotificationTitle => '开启通知';
@@ -202,8 +196,7 @@ class AppLocalizationsZh extends AppLocalizations { @@ -202,8 +196,7 @@ class AppLocalizationsZh extends AppLocalizations {
202 String get onboardingNotificationSubtitle => '及时了解身体每一次异动'; 196 String get onboardingNotificationSubtitle => '及时了解身体每一次异动';
203 197
204 @override 198 @override
205 - String get onboardingNotificationBody =>  
206 - 'AppleWatch数据更新后会及时提醒你,帮助你及时行动,改善压力状态'; 199 + String get onboardingNotificationBody => 'AppleWatch数据更新后会及时提醒你,帮助你及时行动,改善压力状态';
207 200
208 @override 201 @override
209 String get onboardingMemberTitle => '获得年度会员优惠'; 202 String get onboardingMemberTitle => '获得年度会员优惠';
@@ -227,9 +220,7 @@ class AppLocalizationsZh extends AppLocalizations { @@ -227,9 +220,7 @@ class AppLocalizationsZh extends AppLocalizations {
227 String get healthCompanionIsNowAvailable => '健康陪伴已开启'; 220 String get healthCompanionIsNowAvailable => '健康陪伴已开启';
228 221
229 @override 222 @override
230 - String  
231 - get youCanNowViewEachOtherSHrvStressLevelsAndSleepPatternsAndReachOutToCheckInWhenTheOtherPersonSeemsTired =>  
232 - '你们现在可以查看彼此的 HRV、压力与睡眠变化,并在对方疲惫时及时送上关心。'; 223 + String get youCanNowViewEachOtherSHrvStressLevelsAndSleepPatternsAndReachOutToCheckInWhenTheOtherPersonSeemsTired => '你们现在可以查看彼此的 HRV、压力与睡眠变化,并在对方疲惫时及时送上关心。';
233 224
234 @override 225 @override
235 String get bindPartnerTitle => '添加亲密联系人\n多一个人关注你的健康'; 226 String get bindPartnerTitle => '添加亲密联系人\n多一个人关注你的健康';
@@ -133,10 +133,10 @@ packages: @@ -133,10 +133,10 @@ packages:
133 dependency: transitive 133 dependency: transitive
134 description: 134 description:
135 name: characters 135 name: characters
136 - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 136 + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
137 url: "https://pub.dev" 137 url: "https://pub.dev"
138 source: hosted 138 source: hosted
139 - version: "1.4.0" 139 + version: "1.3.0"
140 checked_yaml: 140 checked_yaml:
141 dependency: transitive 141 dependency: transitive
142 description: 142 description:
@@ -149,10 +149,10 @@ packages: @@ -149,10 +149,10 @@ packages:
149 dependency: transitive 149 dependency: transitive
150 description: 150 description:
151 name: clock 151 name: clock
152 - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b 152 + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
153 url: "https://pub.dev" 153 url: "https://pub.dev"
154 source: hosted 154 source: hosted
155 - version: "1.1.2" 155 + version: "1.1.1"
156 code_builder: 156 code_builder:
157 dependency: transitive 157 dependency: transitive
158 description: 158 description:
@@ -165,10 +165,10 @@ packages: @@ -165,10 +165,10 @@ packages:
165 dependency: transitive 165 dependency: transitive
166 description: 166 description:
167 name: collection 167 name: collection
168 - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" 168 + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
169 url: "https://pub.dev" 169 url: "https://pub.dev"
170 source: hosted 170 source: hosted
171 - version: "1.19.1" 171 + version: "1.19.0"
172 convert: 172 convert:
173 dependency: transitive 173 dependency: transitive
174 description: 174 description:
@@ -237,10 +237,10 @@ packages: @@ -237,10 +237,10 @@ packages:
237 dependency: transitive 237 dependency: transitive
238 description: 238 description:
239 name: fake_async 239 name: fake_async
240 - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" 240 + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
241 url: "https://pub.dev" 241 url: "https://pub.dev"
242 source: hosted 242 source: hosted
243 - version: "1.3.3" 243 + version: "1.3.1"
244 ffi: 244 ffi:
245 dependency: transitive 245 dependency: transitive
246 description: 246 description:
@@ -418,7 +418,7 @@ packages: @@ -418,7 +418,7 @@ packages:
418 dependency: transitive 418 dependency: transitive
419 description: 419 description:
420 path: image_cropper_for_web 420 path: image_cropper_for_web
421 - ref: "65c2c99891882ea59732959a672f3d5993a837bb" 421 + ref: "br_v9.1.0_ohos"
422 resolved-ref: "65c2c99891882ea59732959a672f3d5993a837bb" 422 resolved-ref: "65c2c99891882ea59732959a672f3d5993a837bb"
423 url: "https://gitcode.com/openharmony-sig/fluttertpc_image_cropper.git" 423 url: "https://gitcode.com/openharmony-sig/fluttertpc_image_cropper.git"
424 source: git 424 source: git
@@ -427,7 +427,7 @@ packages: @@ -427,7 +427,7 @@ packages:
427 dependency: transitive 427 dependency: transitive
428 description: 428 description:
429 path: image_cropper_platform_interface 429 path: image_cropper_platform_interface
430 - ref: "65c2c99891882ea59732959a672f3d5993a837bb" 430 + ref: "br_v9.1.0_ohos"
431 resolved-ref: "65c2c99891882ea59732959a672f3d5993a837bb" 431 resolved-ref: "65c2c99891882ea59732959a672f3d5993a837bb"
432 url: "https://gitcode.com/openharmony-sig/fluttertpc_image_cropper.git" 432 url: "https://gitcode.com/openharmony-sig/fluttertpc_image_cropper.git"
433 source: git 433 source: git
@@ -509,10 +509,10 @@ packages: @@ -509,10 +509,10 @@ packages:
509 dependency: "direct main" 509 dependency: "direct main"
510 description: 510 description:
511 name: intl 511 name: intl
512 - sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" 512 + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
513 url: "https://pub.dev" 513 url: "https://pub.dev"
514 source: hosted 514 source: hosted
515 - version: "0.20.2" 515 + version: "0.19.0"
516 io: 516 io:
517 dependency: transitive 517 dependency: transitive
518 description: 518 description:
@@ -549,26 +549,26 @@ packages: @@ -549,26 +549,26 @@ packages:
549 dependency: transitive 549 dependency: transitive
550 description: 550 description:
551 name: leak_tracker 551 name: leak_tracker
552 - sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" 552 + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
553 url: "https://pub.dev" 553 url: "https://pub.dev"
554 source: hosted 554 source: hosted
555 - version: "11.0.2" 555 + version: "10.0.7"
556 leak_tracker_flutter_testing: 556 leak_tracker_flutter_testing:
557 dependency: transitive 557 dependency: transitive
558 description: 558 description:
559 name: leak_tracker_flutter_testing 559 name: leak_tracker_flutter_testing
560 - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" 560 + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
561 url: "https://pub.dev" 561 url: "https://pub.dev"
562 source: hosted 562 source: hosted
563 - version: "3.0.10" 563 + version: "3.0.8"
564 leak_tracker_testing: 564 leak_tracker_testing:
565 dependency: transitive 565 dependency: transitive
566 description: 566 description:
567 name: leak_tracker_testing 567 name: leak_tracker_testing
568 - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" 568 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
569 url: "https://pub.dev" 569 url: "https://pub.dev"
570 source: hosted 570 source: hosted
571 - version: "3.0.2" 571 + version: "3.0.1"
572 lints: 572 lints:
573 dependency: transitive 573 dependency: transitive
574 description: 574 description:
@@ -597,10 +597,10 @@ packages: @@ -597,10 +597,10 @@ packages:
597 dependency: transitive 597 dependency: transitive
598 description: 598 description:
599 name: matcher 599 name: matcher
600 - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 600 + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
601 url: "https://pub.dev" 601 url: "https://pub.dev"
602 source: hosted 602 source: hosted
603 - version: "0.12.17" 603 + version: "0.12.16+1"
604 material_color_utilities: 604 material_color_utilities:
605 dependency: transitive 605 dependency: transitive
606 description: 606 description:
@@ -613,10 +613,10 @@ packages: @@ -613,10 +613,10 @@ packages:
613 dependency: transitive 613 dependency: transitive
614 description: 614 description:
615 name: meta 615 name: meta
616 - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" 616 + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
617 url: "https://pub.dev" 617 url: "https://pub.dev"
618 source: hosted 618 source: hosted
619 - version: "1.17.0" 619 + version: "1.15.0"
620 mime: 620 mime:
621 dependency: transitive 621 dependency: transitive
622 description: 622 description:
@@ -645,10 +645,10 @@ packages: @@ -645,10 +645,10 @@ packages:
645 dependency: transitive 645 dependency: transitive
646 description: 646 description:
647 name: path 647 name: path
648 - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" 648 + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
649 url: "https://pub.dev" 649 url: "https://pub.dev"
650 source: hosted 650 source: hosted
651 - version: "1.9.1" 651 + version: "1.9.0"
652 path_provider: 652 path_provider:
653 dependency: transitive 653 dependency: transitive
654 description: 654 description:
@@ -982,18 +982,18 @@ packages: @@ -982,18 +982,18 @@ packages:
982 dependency: transitive 982 dependency: transitive
983 description: 983 description:
984 name: stack_trace 984 name: stack_trace
985 - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" 985 + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
986 url: "https://pub.dev" 986 url: "https://pub.dev"
987 source: hosted 987 source: hosted
988 - version: "1.12.1" 988 + version: "1.12.0"
989 stream_channel: 989 stream_channel:
990 dependency: transitive 990 dependency: transitive
991 description: 991 description:
992 name: stream_channel 992 name: stream_channel
993 - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" 993 + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
994 url: "https://pub.dev" 994 url: "https://pub.dev"
995 source: hosted 995 source: hosted
996 - version: "2.1.4" 996 + version: "2.1.2"
997 stream_transform: 997 stream_transform:
998 dependency: transitive 998 dependency: transitive
999 description: 999 description:
@@ -1022,10 +1022,10 @@ packages: @@ -1022,10 +1022,10 @@ packages:
1022 dependency: "direct main" 1022 dependency: "direct main"
1023 description: 1023 description:
1024 name: table_calendar 1024 name: table_calendar
1025 - sha256: "0c0c6219878b363a2d5f40c7afb159d845f253d061dc3c822aa0d5fe0f721982" 1025 + sha256: b2896b7c86adf3a4d9c911d860120fe3dbe03c85db43b22fd61f14ee78cdbb63
1026 url: "https://pub.dev" 1026 url: "https://pub.dev"
1027 source: hosted 1027 source: hosted
1028 - version: "3.2.0" 1028 + version: "3.1.3"
1029 term_glyph: 1029 term_glyph:
1030 dependency: transitive 1030 dependency: transitive
1031 description: 1031 description:
@@ -1038,10 +1038,10 @@ packages: @@ -1038,10 +1038,10 @@ packages:
1038 dependency: transitive 1038 dependency: transitive
1039 description: 1039 description:
1040 name: test_api 1040 name: test_api
1041 - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 1041 + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
1042 url: "https://pub.dev" 1042 url: "https://pub.dev"
1043 source: hosted 1043 source: hosted
1044 - version: "0.7.7" 1044 + version: "0.7.3"
1045 timing: 1045 timing:
1046 dependency: transitive 1046 dependency: transitive
1047 description: 1047 description:
@@ -1070,10 +1070,10 @@ packages: @@ -1070,10 +1070,10 @@ packages:
1070 dependency: transitive 1070 dependency: transitive
1071 description: 1071 description:
1072 name: vector_math 1072 name: vector_math
1073 - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b 1073 + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
1074 url: "https://pub.dev" 1074 url: "https://pub.dev"
1075 source: hosted 1075 source: hosted
1076 - version: "2.2.0" 1076 + version: "2.1.4"
1077 vm_service: 1077 vm_service:
1078 dependency: transitive 1078 dependency: transitive
1079 description: 1079 description:
@@ -1127,8 +1127,8 @@ packages: @@ -1127,8 +1127,8 @@ packages:
1127 dependency: transitive 1127 dependency: transitive
1128 description: 1128 description:
1129 path: "packages/webview_flutter/webview_flutter_android" 1129 path: "packages/webview_flutter/webview_flutter_android"
1130 - ref: de942e79c9057b32ad31106508bd87c0d60aef83  
1131 - resolved-ref: de942e79c9057b32ad31106508bd87c0d60aef83 1130 + ref: "br_webview_flutter-v4.13.0_ohos"
  1131 + resolved-ref: "8d70938aa190eb7d7cd4d66f0d16fddb3c2d03ba"
1132 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git" 1132 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
1133 source: git 1133 source: git
1134 version: "4.7.0" 1134 version: "4.7.0"
@@ -1136,8 +1136,8 @@ packages: @@ -1136,8 +1136,8 @@ packages:
1136 dependency: transitive 1136 dependency: transitive
1137 description: 1137 description:
1138 path: "packages/webview_flutter/webview_flutter_ohos" 1138 path: "packages/webview_flutter/webview_flutter_ohos"
1139 - ref: de942e79c9057b32ad31106508bd87c0d60aef83  
1140 - resolved-ref: de942e79c9057b32ad31106508bd87c0d60aef83 1139 + ref: "br_webview_flutter-v4.13.0_ohos"
  1140 + resolved-ref: "8d70938aa190eb7d7cd4d66f0d16fddb3c2d03ba"
1141 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git" 1141 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
1142 source: git 1142 source: git
1143 version: "4.7.0" 1143 version: "4.7.0"
@@ -1145,8 +1145,8 @@ packages: @@ -1145,8 +1145,8 @@ packages:
1145 dependency: transitive 1145 dependency: transitive
1146 description: 1146 description:
1147 path: "packages/webview_flutter/webview_flutter_platform_interface" 1147 path: "packages/webview_flutter/webview_flutter_platform_interface"
1148 - ref: de942e79c9057b32ad31106508bd87c0d60aef83  
1149 - resolved-ref: de942e79c9057b32ad31106508bd87c0d60aef83 1148 + ref: "br_webview_flutter-v4.13.0_ohos"
  1149 + resolved-ref: "8d70938aa190eb7d7cd4d66f0d16fddb3c2d03ba"
1150 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git" 1150 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
1151 source: git 1151 source: git
1152 version: "2.13.1" 1152 version: "2.13.1"
@@ -1154,8 +1154,8 @@ packages: @@ -1154,8 +1154,8 @@ packages:
1154 dependency: transitive 1154 dependency: transitive
1155 description: 1155 description:
1156 path: "packages/webview_flutter/webview_flutter_wkwebview" 1156 path: "packages/webview_flutter/webview_flutter_wkwebview"
1157 - ref: de942e79c9057b32ad31106508bd87c0d60aef83  
1158 - resolved-ref: de942e79c9057b32ad31106508bd87c0d60aef83 1157 + ref: "br_webview_flutter-v4.13.0_ohos"
  1158 + resolved-ref: "8d70938aa190eb7d7cd4d66f0d16fddb3c2d03ba"
1159 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git" 1159 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
1160 source: git 1160 source: git
1161 version: "3.22.0" 1161 version: "3.22.0"
@@ -1176,5 +1176,5 @@ packages: @@ -1176,5 +1176,5 @@ packages:
1176 source: hosted 1176 source: hosted
1177 version: "3.1.3" 1177 version: "3.1.3"
1178 sdks: 1178 sdks:
1179 - dart: ">=3.8.0-0 <4.0.0" 1179 + dart: ">=3.6.2 <4.0.0"
1180 flutter: ">=3.27.0" 1180 flutter: ">=3.27.0"