Commit 65e173994d961d1a3c4798c0cee64d5a7ba43239

Authored by 常守达
1 parent 68edda64

fix(today):看板问题修复

@@ -84,7 +84,7 @@ class BindPartnerController extends GetxController { @@ -84,7 +84,7 @@ class BindPartnerController extends GetxController {
84 84
85 void onShareMyCode() async { 85 void onShareMyCode() async {
86 try { 86 try {
87 - ta.track('enter_doublefeel_add_friend_page', 87 + ta.track('click_doublefeel_add_friend_page',
88 properties: {'channel_type': channel, 'click_content': '分享我的码'}); 88 properties: {'channel_type': channel, 'click_content': '分享我的码'});
89 final success = await PlatformHostApi() 89 final success = await PlatformHostApi()
90 .shareText(l10n.sharePartnerCodeTemplate(myInviteCode.value)); 90 .shareText(l10n.sharePartnerCodeTemplate(myInviteCode.value));
@@ -94,7 +94,7 @@ class BindPartnerController extends GetxController { @@ -94,7 +94,7 @@ class BindPartnerController extends GetxController {
94 } 94 }
95 95
96 Future<void> onSubmitPartnerId() async { 96 Future<void> onSubmitPartnerId() async {
97 - ta.track('enter_doublefeel_add_friend_page', 97 + ta.track('click_doublefeel_add_friend_page',
98 properties: {'channel_type': channel, 'click_content': '继续'}); 98 properties: {'channel_type': channel, 'click_content': '继续'});
99 if (!canSubmit || isSubmitting.value) return; 99 if (!canSubmit || isSubmitting.value) return;
100 isSubmitting.value = true; 100 isSubmitting.value = true;
@@ -152,7 +152,7 @@ class BindPartnerController extends GetxController { @@ -152,7 +152,7 @@ class BindPartnerController extends GetxController {
152 } 152 }
153 153
154 Future<void> onSkip() async { 154 Future<void> onSkip() async {
155 - ta.track('enter_doublefeel_add_friend_page', 155 + ta.track('click_doublefeel_add_friend_page',
156 properties: {'channel_type': channel, 'click_content': '下次再说'}); 156 properties: {'channel_type': channel, 'click_content': '下次再说'});
157 final userStateService = Get.find<UserStateService>(); 157 final userStateService = Get.find<UserStateService>();
158 if (!userStateService.isVip) { 158 if (!userStateService.isVip) {
@@ -44,33 +44,13 @@ class BindPartnerView extends GetView<BindPartnerController> { @@ -44,33 +44,13 @@ class BindPartnerView extends GetView<BindPartnerController> {
44 physics: const ClampingScrollPhysics(), 44 physics: const ClampingScrollPhysics(),
45 children: [ 45 children: [
46 _buildTitle(context), 46 _buildTitle(context),
47 - SizedBox(height: 28), 47 + // SizedBox(height: 28),
48 _buildCardArea(context), 48 _buildCardArea(context),
49 - SizedBox(height: 58), 49 + // SizedBox(height: 58),
50 if (controller.isShowSkipButton) _buildSkipButton(context), 50 if (controller.isShowSkipButton) _buildSkipButton(context),
51 ], 51 ],
52 ), 52 ),
53 ), 53 ),
54 - Positioned(  
55 - top: MediaQuery.paddingOf(context).top + 92,  
56 - left: 0,  
57 - child: Image.asset(  
58 - 'assets/images/user_onboarding/ic_bind_decor_top.png',  
59 - width: 133,  
60 - height: 124,  
61 - fit: BoxFit.fitHeight,  
62 - ),  
63 - ),  
64 - Positioned(  
65 - top: MediaQuery.paddingOf(context).top + 532,  
66 - right: 0,  
67 - child: Image.asset(  
68 - 'assets/images/user_onboarding/ic_bind_decor_bottom.png',  
69 - width: 133,  
70 - height: 124,  
71 - fit: BoxFit.fitHeight,  
72 - ),  
73 - ),  
74 ], 54 ],
75 ), 55 ),
76 ), 56 ),
@@ -116,48 +96,73 @@ class BindPartnerView extends GetView<BindPartnerController> { @@ -116,48 +96,73 @@ class BindPartnerView extends GetView<BindPartnerController> {
116 } 96 }
117 97
118 Widget _buildCardArea(BuildContext context) { 98 Widget _buildCardArea(BuildContext context) {
119 - return Container(  
120 - margin: EdgeInsets.symmetric(horizontal: 24),  
121 - decoration: BoxDecoration(  
122 - borderRadius: BorderRadius.circular(20),  
123 - image: DecorationImage(  
124 - image:  
125 - AssetImage('assets/images/friends/bg_friend_add_subtract.webp'),  
126 - fit: BoxFit.cover, 99 + return Stack(
  100 + children: [
  101 + Container(
  102 + margin: EdgeInsets.only(top: 28, bottom: 64, left: 24, right: 24),
  103 + decoration: BoxDecoration(
  104 + borderRadius: BorderRadius.circular(20),
  105 + image: DecorationImage(
  106 + image: AssetImage(
  107 + 'assets/images/friends/bg_friend_add_subtract.webp'),
  108 + fit: BoxFit.cover,
  109 + ),
  110 + boxShadow: [
  111 + BoxShadow(
  112 + color: context.colors.primary.withValues(alpha: 0.08),
  113 + blurRadius: 24,
  114 + offset: const Offset(0, 8),
  115 + ),
  116 + ],
  117 + ),
  118 + child: Column(
  119 + children: [
  120 + SizedBox(height: 28),
  121 + _buildMyInfoSection(context),
  122 + SizedBox(height: 40),
  123 + _buildOrDivider(context),
  124 + SizedBox(height: 28),
  125 + _buildInputSection(context),
  126 + SizedBox(height: 16),
  127 + // 继续按钮
  128 + Container(
  129 + padding: EdgeInsets.symmetric(horizontal: 32),
  130 + child: Obx(() {
  131 + return _buildPrimaryButton(
  132 + label: context.l10n.continueButton,
  133 + onTap: controller.onSubmitPartnerId,
  134 + enabled:
  135 + controller.canSubmit && !controller.isSubmitting.value,
  136 + isLoading: controller.isSubmitting.value,
  137 + primaryColor: context.colors.chartOrange,
  138 + );
  139 + }),
  140 + ),
  141 + SizedBox(height: 40),
  142 + ],
  143 + ),
127 ), 144 ),
128 - boxShadow: [  
129 - BoxShadow(  
130 - color: context.colors.primary.withValues(alpha: 0.08),  
131 - blurRadius: 24,  
132 - offset: const Offset(0, 8), 145 + Positioned(
  146 + top: 0,
  147 + left: 0,
  148 + child: Image.asset(
  149 + 'assets/images/user_onboarding/ic_bind_decor_top.png',
  150 + width: 133,
  151 + height: 124,
  152 + fit: BoxFit.fitHeight,
133 ), 153 ),
134 - ],  
135 - ),  
136 - child: Column(  
137 - children: [  
138 - SizedBox(height: 28),  
139 - _buildMyInfoSection(context),  
140 - SizedBox(height: 40),  
141 - _buildOrDivider(context),  
142 - SizedBox(height: 28),  
143 - _buildInputSection(context),  
144 - SizedBox(height: 16),  
145 - // 继续按钮  
146 - Container(  
147 - padding: EdgeInsets.symmetric(horizontal: 32),  
148 - child: Obx(() {  
149 - return _buildPrimaryButton(  
150 - label: context.l10n.continueButton,  
151 - onTap: controller.onSubmitPartnerId,  
152 - enabled: controller.canSubmit && !controller.isSubmitting.value,  
153 - isLoading: controller.isSubmitting.value,  
154 - primaryColor: context.colors.chartOrange,  
155 - );  
156 - }), 154 + ),
  155 + Positioned(
  156 + bottom: 0,
  157 + right: 0,
  158 + child: Image.asset(
  159 + 'assets/images/user_onboarding/ic_bind_decor_bottom.png',
  160 + width: 133,
  161 + height: 124,
  162 + fit: BoxFit.fitHeight,
157 ), 163 ),
158 - SizedBox(height: 40),  
159 - ],  
160 - ), 164 + ),
  165 + ],
161 ); 166 );
162 } 167 }
163 168
@@ -199,7 +204,7 @@ class BindPartnerView extends GetView<BindPartnerController> { @@ -199,7 +204,7 @@ class BindPartnerView extends GetView<BindPartnerController> {
199 ClipboardData(text: controller.myInviteCode.value), 204 ClipboardData(text: controller.myInviteCode.value),
200 ); 205 );
201 AppToast.show('复制成功'); 206 AppToast.show('复制成功');
202 - ta.track('enter_doublefeel_add_friend_page', properties: { 207 + ta.track('click_doublefeel_add_friend_page', properties: {
203 'channel_type': controller.channel, 208 'channel_type': controller.channel,
204 'click_content': '复制共享码' 209 'click_content': '复制共享码'
205 }); 210 });
@@ -294,24 +299,45 @@ class BindPartnerView extends GetView<BindPartnerController> { @@ -294,24 +299,45 @@ class BindPartnerView extends GetView<BindPartnerController> {
294 border: Border.all(color: const Color(0xFFF0F0F6), width: 1), 299 border: Border.all(color: const Color(0xFFF0F0F6), width: 1),
295 borderRadius: BorderRadius.circular(27), 300 borderRadius: BorderRadius.circular(27),
296 ), 301 ),
297 - child: TextField(  
298 - controller: controller.partnerIdController,  
299 - textAlign: TextAlign.center,  
300 - style: TextStyle(  
301 - fontSize: 32,  
302 - fontWeight: FontWeight.w400,  
303 - color: context.colors.textPrimary,  
304 - ),  
305 - decoration: InputDecoration(  
306 - hintText: context.l10n.bindPartnerInputHint,  
307 - hintStyle: TextStyle(  
308 - fontSize: 16,  
309 - fontWeight: FontWeight.w400,  
310 - color: const Color(0xFFD0D0D2), 302 + child: Stack(
  303 + alignment: Alignment.center, // 确保 Stack 里的子项绝对居中
  304 + children: [
  305 + // 自定义 Hint 文字层
  306 + Obx(() => controller.partnerIdInput.value.isEmpty
  307 + ? IgnorePointer(
  308 + child: Text(
  309 + context.l10n.bindPartnerInputHint,
  310 + style: const TextStyle(
  311 + fontSize: 16,
  312 + fontWeight: FontWeight.w400,
  313 + color: Color(0xFFD0D0D2),
  314 + height: 1.1,
  315 + ),
  316 + ),
  317 + )
  318 + : const SizedBox()),
  319 + // 真正的输入框层(不设 hintText)
  320 + TextField(
  321 + controller: controller.partnerIdController,
  322 + textAlign: TextAlign.center,
  323 + textAlignVertical: TextAlignVertical.center,
  324 + style: TextStyle(
  325 + fontSize: 32,
  326 + fontWeight: FontWeight.w400,
  327 + color: context.colors.textPrimary,
  328 + height: 1.1,
  329 + ),
  330 + cursorWidth: 2.0,
  331 + cursorHeight: 24.0,
  332 + cursorRadius: const Radius.circular(1.5),
  333 + decoration: const InputDecoration(
  334 + border: InputBorder.none,
  335 + isCollapsed: true,
  336 + contentPadding:
  337 + EdgeInsets.symmetric(horizontal: 20, vertical: 0),
  338 + ),
311 ), 339 ),
312 - border: InputBorder.none,  
313 - contentPadding: EdgeInsets.symmetric(horizontal: 20),  
314 - ), 340 + ],
315 ), 341 ),
316 ), 342 ),
317 ], 343 ],
@@ -7,6 +7,7 @@ import 'package:doublefeel_flutter/app/modules/home/controllers/trend/trend_cont @@ -7,6 +7,7 @@ import 'package:doublefeel_flutter/app/modules/home/controllers/trend/trend_cont
7 import 'package:doublefeel_flutter/app/modules/home/widgets/today/friend_select_bottom_sheet.dart'; 7 import 'package:doublefeel_flutter/app/modules/home/widgets/today/friend_select_bottom_sheet.dart';
8 import 'package:doublefeel_flutter/app/modules/home/widgets/today/no_health_data_page.dart'; 8 import 'package:doublefeel_flutter/app/modules/home/widgets/today/no_health_data_page.dart';
9 import 'package:doublefeel_flutter/app/routes/app_pages.dart'; 9 import 'package:doublefeel_flutter/app/routes/app_pages.dart';
  10 +import 'package:doublefeel_flutter/core/constants/intent_keys.dart';
10 import 'package:doublefeel_flutter/core/constants/membership.dart'; 11 import 'package:doublefeel_flutter/core/constants/membership.dart';
11 import 'package:doublefeel_flutter/core/logging/app_logger.dart'; 12 import 'package:doublefeel_flutter/core/logging/app_logger.dart';
12 import 'package:doublefeel_flutter/core/network/api/friend_api.dart'; 13 import 'package:doublefeel_flutter/core/network/api/friend_api.dart';
@@ -190,35 +191,42 @@ class TodayController extends GetMaterialController { @@ -190,35 +191,42 @@ class TodayController extends GetMaterialController {
190 } 191 }
191 192
192 void checkAddFriendVisible() { 193 void checkAddFriendVisible() {
193 - _refreshFriendList(onFriendListUpdated: () {  
194 - if (friendsList.isNotEmpty ||  
195 - friendsList.value.length >= friendsListLimit) {  
196 - showPartnerAdBanner.value = false;  
197 - return;  
198 - }  
199 - // 关闭后 3 天内不再显示  
200 - final userId =  
201 - Get.find<UserPreferencesStorage>().preferences.value.meUserInfo?.id ??  
202 - 0;  
203 - final lastShowTime =  
204 - Get.find<UserAccountStorage>().getLastAddFriendBannerShowTime(userId);  
205 - if (lastShowTime != null) {  
206 - final diff = DateTime.now()  
207 - .difference(DateTime.fromMillisecondsSinceEpoch(lastShowTime));  
208 - if (diff.inDays < 3) { 194 + if (isFriend) {
  195 + showPartnerAdBanner.value = false;
  196 + } else {
  197 + _refreshFriendList(onFriendListUpdated: () {
  198 + if (friendsList.isNotEmpty ||
  199 + friendsList.value.length >= friendsListLimit) {
209 showPartnerAdBanner.value = false; 200 showPartnerAdBanner.value = false;
210 return; 201 return;
211 } 202 }
212 - }  
213 - showPartnerAdBanner.value = true;  
214 - }); 203 + // 关闭后 3 天内不再显示
  204 + final userId = Get.find<UserPreferencesStorage>()
  205 + .preferences
  206 + .value
  207 + .meUserInfo
  208 + ?.id ??
  209 + 0;
  210 + final lastShowTime = Get.find<UserAccountStorage>()
  211 + .getLastAddFriendBannerShowTime(userId);
  212 + if (lastShowTime != null) {
  213 + final diff = DateTime.now()
  214 + .difference(DateTime.fromMillisecondsSinceEpoch(lastShowTime));
  215 + if (diff.inDays < 3) {
  216 + showPartnerAdBanner.value = false;
  217 + return;
  218 + }
  219 + }
  220 + showPartnerAdBanner.value = true;
  221 + });
  222 + }
215 } 223 }
216 224
217 void changeDate(DateTime date, {DateTime? focused}) { 225 void changeDate(DateTime date, {DateTime? focused}) {
218 final normalizedDate = _clampDate(date); 226 final normalizedDate = _clampDate(date);
219 if (!DateUtils.isSameDay(date, lastSelectableDay) && 227 if (!DateUtils.isSameDay(date, lastSelectableDay) &&
220 !_userStateService.isVip) { 228 !_userStateService.isVip) {
221 - toPremiumPage(); 229 + toPremiumPage('今日-查看非当日数据');
222 return; 230 return;
223 } 231 }
224 selectedDate.value = normalizedDate; 232 selectedDate.value = normalizedDate;
@@ -334,20 +342,40 @@ class TodayController extends GetMaterialController { @@ -334,20 +342,40 @@ class TodayController extends GetMaterialController {
334 342
335 Future<void> checkHealthDataAuthCardVisible( 343 Future<void> checkHealthDataAuthCardVisible(
336 [HealthAuthorization? preResult]) async { 344 [HealthAuthorization? preResult]) async {
337 - try {  
338 - final result = preResult ?? await _hostApi.checkHealthAppAuthorization();  
339 - ta.setSuperProperties(  
340 - {'health_data_permission': result.status.clamp(0, 1)});  
341 - if (result.status == 1) {  
342 - await _performDataUpload();  
343 - }  
344 - final hasUploaded =  
345 - switch (await _healthApi.getHealthDataEverUploaded()) {  
346 - AppSuccess(:final data) => data,  
347 - AppFailure() => false,  
348 - };  
349 - showHealthDataAuthCardStatus.value = !hasUploaded ? -1 : result.status;  
350 - } catch (e) {} 345 + final firstTime = DateUtils.dateOnly(
  346 + DateTime(lastSelectableDay.year, lastSelectableDay.month - 6,
  347 + lastSelectableDay.day),
  348 + );
  349 + if (isFriend) {
  350 + showHealthDataAuthCardStatus.value = 1;
  351 + firstSelectableDay.value = firstTime;
  352 + } else {
  353 + try {
  354 + final result =
  355 + preResult ?? await _hostApi.checkHealthAppAuthorization();
  356 + ta.setSuperProperties(
  357 + {'health_data_permission': result.status.clamp(0, 1)});
  358 + if (result.status == 1) {
  359 + await _performDataUpload();
  360 + }
  361 + bool hasUploaded = false;
  362 + switch (await _healthApi.getHealthDataEverUploaded()) {
  363 + case AppSuccess(:final data):
  364 + hasUploaded = data.flag ?? false;
  365 + if (hasUploaded && data.startTime != null && data.startTime! > 0) {
  366 + final firstDayTime = DateUtils.dateOnly(
  367 + DateTime.fromMillisecondsSinceEpoch(data.startTime! * 1000),
  368 + );
  369 + if (firstDayTime.isBefore(firstTime)) {
  370 + firstSelectableDay.value = firstDayTime;
  371 + }
  372 + }
  373 + case AppFailure():
  374 + hasUploaded = false;
  375 + }
  376 + showHealthDataAuthCardStatus.value = !hasUploaded ? -1 : result.status;
  377 + } catch (e) {}
  378 + }
351 } 379 }
352 380
353 Future<void> checkNotificationCardVisible() async { 381 Future<void> checkNotificationCardVisible() async {
@@ -388,12 +416,6 @@ class TodayController extends GetMaterialController { @@ -388,12 +416,6 @@ class TodayController extends GetMaterialController {
388 switch (await _healthApi.getV2StressScore(friendUserId, intDate)) { 416 switch (await _healthApi.getV2StressScore(friendUserId, intDate)) {
389 case AppSuccess(:final data): 417 case AppSuccess(:final data):
390 v2StressScore.value = data; 418 v2StressScore.value = data;
391 -  
392 - // var nextInt = Random().nextInt(4);  
393 - // v2StressScore.value = V2StressScore(  
394 - // state: nextInt,  
395 - // comprehensiveScore: 100 - (nextInt * 25),  
396 - // );  
397 case AppFailure(): 419 case AppFailure():
398 v2StressScore.value = null; 420 v2StressScore.value = null;
399 } 421 }
@@ -408,25 +430,6 @@ class TodayController extends GetMaterialController { @@ -408,25 +430,6 @@ class TodayController extends GetMaterialController {
408 switch (await _healthApi.getV2HrvTrend(friendUserId, intDate)) { 430 switch (await _healthApi.getV2HrvTrend(friendUserId, intDate)) {
409 case AppSuccess(:final data): 431 case AppSuccess(:final data):
410 hrvChartData.assignAll(data.list ?? []); 432 hrvChartData.assignAll(data.list ?? []);
411 - // hrvChartData.assignAll([  
412 - // // 每小时一条,00:00 ~ 15:00(CST 午夜 = 1782057600)  
413 - // V2HrvTrendItem(time: 1782057600, trendHrv: 45, state: 3), // 00:00  
414 - // V2HrvTrendItem(time: 1782061200, trendHrv: 42, state: 3), // 01:00  
415 - // V2HrvTrendItem(time: 1782064800, trendHrv: 38, state: 3), // 02:00  
416 - // V2HrvTrendItem(time: 1782068400, trendHrv: 35, state: 2), // 03:00  
417 - // V2HrvTrendItem(time: 1782072000, trendHrv: 40, state: 3), // 04:00  
418 - // V2HrvTrendItem(time: 1782075600, trendHrv: 55, state: 4), // 05:00  
419 - // V2HrvTrendItem(time: 1782079200, trendHrv: 65, state: 4), // 06:00  
420 - // V2HrvTrendItem(time: 1782082800, trendHrv: 72, state: 4), // 07:00  
421 - // V2HrvTrendItem(time: 1782086400, trendHrv: 68, state: 4), // 08:00  
422 - // V2HrvTrendItem(time: 1782090000, trendHrv: 58, state: 4), // 09:00  
423 - // V2HrvTrendItem(time: 1782093600, trendHrv: 52, state: 3), // 10:00  
424 - // V2HrvTrendItem(time: 1782097200, trendHrv: 48, state: 3), // 11:00  
425 - // V2HrvTrendItem(time: 1782100800, trendHrv: 44, state: 3), // 12:00  
426 - // V2HrvTrendItem(time: 1782104400, trendHrv: 38, state: 2), // 13:00  
427 - // V2HrvTrendItem(time: 1782108000, trendHrv: 42, state: 3), // 14:00  
428 - // V2HrvTrendItem(time: 1782111600, trendHrv: 45, state: 3), // 15:00  
429 - // ]);  
430 case AppFailure(): 433 case AppFailure():
431 hrvChartData.clear(); 434 hrvChartData.clear();
432 } 435 }
@@ -434,40 +437,6 @@ class TodayController extends GetMaterialController { @@ -434,40 +437,6 @@ class TodayController extends GetMaterialController {
434 switch (await _healthApi.getV2RealtimeStress(friendUserId, intDate)) { 437 switch (await _healthApi.getV2RealtimeStress(friendUserId, intDate)) {
435 case AppSuccess(:final data): 438 case AppSuccess(:final data):
436 stressChartData.assignAll(data.list ?? []); 439 stressChartData.assignAll(data.list ?? []);
437 - // stressChartData.assignAll(  
438 - // // 每 6 分钟一条,00:00 ~ 15:00(共 150 条)  
439 - // // midnight CST = 1782057600,步长 360s  
440 - // List.generate(150, (i) {  
441 - // const midnight = 1782057600;  
442 - // final ts = midnight + i * 360;  
443 - // final minutesFromMidnight = i * 6;  
444 - // final hour = minutesFromMidnight ~/ 60;  
445 - // final minInHour = minutesFromMidnight % 60;  
446 - // // 用正弦波模拟真实波动,早高峰(7-9点)压力最高  
447 - // final base = hour < 6  
448 - // ? 25.0 // 深夜低压力  
449 - // : hour < 9  
450 - // ? 55.0 + (hour - 6) * 10.0 // 早高峰爬升  
451 - // : hour < 12  
452 - // ? 75.0 - (hour - 9) * 8.0 // 上午下降  
453 - // : 50.0 - (hour - 12) * 3.0; // 下午缓降  
454 - // // 叠加微小波动(用 i 模拟随机)  
455 - // final jitter = (i % 7 - 3) * 2.0 + (minInHour % 3) * 1.5;  
456 - // final value = (base + jitter).clamp(10.0, 100.0).round();  
457 - // final int state;  
458 - // if (value >= 75) {  
459 - // state = 1;  
460 - // } else if (value >= 55) {  
461 - // state = 2;  
462 - // } else if (value >= 35) {  
463 - // state = 3;  
464 - // } else {  
465 - // state = 4;  
466 - // }  
467 - // return V2RealtimeStressItem(time: ts, value: value, state: state);  
468 - // }),  
469 - // );  
470 -  
471 case AppFailure(): 440 case AppFailure():
472 stressChartData.clear(); 441 stressChartData.clear();
473 } 442 }
@@ -479,12 +448,6 @@ class TodayController extends GetMaterialController { @@ -479,12 +448,6 @@ class TodayController extends GetMaterialController {
479 v2ActivityTarget.value = null; 448 v2ActivityTarget.value = null;
480 } 449 }
481 } 450 }
482 - // Future.delayed(const Duration(milliseconds: 5000), () {  
483 - // firstSelectableDay.value = DateUtils.dateOnly(  
484 - // DateTime(  
485 - // DateTime.now().year, DateTime.now().month, DateTime.now().day - 1),  
486 - // );  
487 - // });  
488 } 451 }
489 452
490 void _clearRealTimeData() { 453 void _clearRealTimeData() {
@@ -588,8 +551,11 @@ class TodayController extends GetMaterialController { @@ -588,8 +551,11 @@ class TodayController extends GetMaterialController {
588 RxList<FriendItem> friendsList = RxList.empty(); 551 RxList<FriendItem> friendsList = RxList.empty();
589 int friendsListLimit = 10; 552 int friendsListLimit = 10;
590 553
591 - Future<void> toPremiumPage() async {  
592 - await Get.toNamed(Routes.PURCHASE); 554 + Future<void> toPremiumPage(String channelType) async {
  555 + await Get.toNamed(
  556 + Routes.PURCHASE,
  557 + arguments: {IntentKeys.channelType: channelType},
  558 + );
593 final vipResult = await _vipApi.getVipInfo(); 559 final vipResult = await _vipApi.getVipInfo();
594 if (vipResult case AppSuccess(data: final vip)) { 560 if (vipResult case AppSuccess(data: final vip)) {
595 try { 561 try {
@@ -688,4 +654,11 @@ class TodayController extends GetMaterialController { @@ -688,4 +654,11 @@ class TodayController extends GetMaterialController {
688 unawaited(loadDataForDate(selectedDate.value)); 654 unawaited(loadDataForDate(selectedDate.value));
689 } 655 }
690 } 656 }
  657 +
  658 + Future<void> refreshTab() async {
  659 + checkAddFriendVisible();
  660 + checkHrvAdBannerVisible();
  661 + checkHealthDataAuthCardVisible();
  662 + await loadDataForDate(selectedDate.value);
  663 + }
691 } 664 }
@@ -60,7 +60,11 @@ class TodayTabBody extends StatelessWidget { @@ -60,7 +60,11 @@ class TodayTabBody extends StatelessWidget {
60 } 60 }
61 return false; 61 return false;
62 }, 62 },
63 - child: _buildDayScrollView(context, pinnedHeaderHeight), 63 + child: RefreshIndicator(
  64 + edgeOffset: pinnedHeaderHeight,
  65 + onRefresh: () async => controller.refreshTab(),
  66 + child: _buildDayScrollView(context, pinnedHeaderHeight),
  67 + ),
64 ), 68 ),
65 Positioned( 69 Positioned(
66 top: 0, 70 top: 0,
@@ -121,7 +125,9 @@ class TodayTabBody extends StatelessWidget { @@ -121,7 +125,9 @@ class TodayTabBody extends StatelessWidget {
121 ) { 125 ) {
122 return CustomScrollView( 126 return CustomScrollView(
123 controller: controller.scrollController, 127 controller: controller.scrollController,
124 - physics: const ClampingScrollPhysics(), 128 + physics: const AlwaysScrollableScrollPhysics(
  129 + parent: ClampingScrollPhysics(),
  130 + ),
125 slivers: [ 131 slivers: [
126 SliverToBoxAdapter( 132 SliverToBoxAdapter(
127 child: Container( 133 child: Container(
@@ -322,6 +328,7 @@ class TodayTabBody extends StatelessWidget { @@ -322,6 +328,7 @@ class TodayTabBody extends StatelessWidget {
322 child: SizedBox( 328 child: SizedBox(
323 height: _weekRowsHeight, 329 height: _weekRowsHeight,
324 child: TableCalendar( 330 child: TableCalendar(
  331 + key: ValueKey(controller.firstSelectableDay.value),
325 calendarFormat: CalendarFormat.week, 332 calendarFormat: CalendarFormat.week,
326 headerVisible: false, 333 headerVisible: false,
327 daysOfWeekHeight: _weekRowsHeight / 2, 334 daysOfWeekHeight: _weekRowsHeight / 2,
@@ -375,7 +382,7 @@ class TodayTabBody extends StatelessWidget { @@ -375,7 +382,7 @@ class TodayTabBody extends StatelessWidget {
375 GestureDetector( 382 GestureDetector(
376 onTap: () async { 383 onTap: () async {
377 if (!Get.find<UserStateService>().isVip) { 384 if (!Get.find<UserStateService>().isVip) {
378 - controller.toPremiumPage(); 385 + controller.toPremiumPage('今日-查看非当日数据');
379 return; 386 return;
380 } 387 }
381 final picked = await showReportDatePickerSheet( 388 final picked = await showReportDatePickerSheet(
@@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart'; @@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
2 import 'package:doublefeel_flutter/app/modules/home/controllers/today_controller.dart'; 2 import 'package:doublefeel_flutter/app/modules/home/controllers/today_controller.dart';
3 import 'package:doublefeel_flutter/core/theme/app_theme.dart'; 3 import 'package:doublefeel_flutter/core/theme/app_theme.dart';
4 import 'package:doublefeel_flutter/data/models/friend/friend_models.dart'; 4 import 'package:doublefeel_flutter/data/models/friend/friend_models.dart';
  5 +import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
5 import 'package:flutter/material.dart'; 6 import 'package:flutter/material.dart';
6 import 'package:get/get.dart'; 7 import 'package:get/get.dart';
7 8
@@ -33,7 +34,7 @@ class FriendSelectBottomSheet extends StatelessWidget { @@ -33,7 +34,7 @@ class FriendSelectBottomSheet extends StatelessWidget {
33 children: [ 34 children: [
34 Center( 35 Center(
35 child: Text( 36 child: Text(
36 - '选择好友', 37 + l10n.friendsSelect,
37 style: TextStyle( 38 style: TextStyle(
38 color: context.colors.textPrimary, 39 color: context.colors.textPrimary,
39 fontSize: 16, 40 fontSize: 16,
@@ -79,6 +80,7 @@ class FriendSelectBottomSheet extends StatelessWidget { @@ -79,6 +80,7 @@ class FriendSelectBottomSheet extends StatelessWidget {
79 friend: friend, 80 friend: friend,
80 isSelected: isSelected, 81 isSelected: isSelected,
81 onTap: () { 82 onTap: () {
  83 + Get.back();
82 controller.selectFriend(friend); 84 controller.selectFriend(friend);
83 }, 85 },
84 ); 86 );
@@ -151,7 +151,7 @@ class PremiumCard extends StatelessWidget { @@ -151,7 +151,7 @@ class PremiumCard extends StatelessWidget {
151 mainAxisAlignment: MainAxisAlignment.spaceBetween, 151 mainAxisAlignment: MainAxisAlignment.spaceBetween,
152 children: [ 152 children: [
153 GestureDetector( 153 GestureDetector(
154 - onTap: () => controller.toPremiumPage(), 154 + onTap: () => controller.toPremiumPage('今日-会员广告'),
155 behavior: HitTestBehavior.opaque, 155 behavior: HitTestBehavior.opaque,
156 child: Text( 156 child: Text(
157 context.l10n.allPlans, 157 context.l10n.allPlans,
@@ -256,6 +256,7 @@ class _ContactText extends StatelessWidget { @@ -256,6 +256,7 @@ class _ContactText extends StatelessWidget {
256 child: Text( 256 child: Text(
257 l10n.todayFaqContactAction, 257 l10n.todayFaqContactAction,
258 style: TextStyle( 258 style: TextStyle(
  259 + fontSize: 12,
259 color: context.colors.primary, 260 color: context.colors.primary,
260 fontWeight: FontWeight.w600, 261 fontWeight: FontWeight.w600,
261 decoration: TextDecoration.underline, 262 decoration: TextDecoration.underline,
@@ -67,7 +67,7 @@ class TodayHealthDataAuthCard extends StatelessWidget { @@ -67,7 +67,7 @@ class TodayHealthDataAuthCard extends StatelessWidget {
67 style: TextStyle( 67 style: TextStyle(
68 color: colors.primary, 68 color: colors.primary,
69 fontSize: 12, 69 fontSize: 12,
70 - fontWeight: FontWeight.w500, 70 + fontWeight: FontWeight.w600,
71 height: 1.2, 71 height: 1.2,
72 ), 72 ),
73 ), 73 ),
@@ -238,7 +238,7 @@ class TodayHrvChartCard extends StatelessWidget { @@ -238,7 +238,7 @@ class TodayHrvChartCard extends StatelessWidget {
238 DateTime selectedDate, 238 DateTime selectedDate,
239 bool isVip) { 239 bool isVip) {
240 void onTapPurchase() { 240 void onTapPurchase() {
241 - controller.toPremiumPage(); 241 + controller.toPremiumPage('今日-实时压力-立即解锁');
242 } 242 }
243 243
244 // 槽时间秒 → state 映射(与 _buildStressBarGroups 一致:取 value 最大那条 of state) 244 // 槽时间秒 → state 映射(与 _buildStressBarGroups 一致:取 value 最大那条 of state)
@@ -384,8 +384,17 @@ class TodayHrvChartCard extends StatelessWidget { @@ -384,8 +384,17 @@ class TodayHrvChartCard extends StatelessWidget {
384 List<TextSpan>? _getTooltip(BarChartRodData rod, int x, int? state) { 384 List<TextSpan>? _getTooltip(BarChartRodData rod, int x, int? state) {
385 return [ 385 return [
386 TextSpan( 386 TextSpan(
387 - text: '○ ${_getStatusByState(state)} ${_formatNumber(rod.toY)}%', 387 + text: '○ ',
388 style: TextStyle( 388 style: TextStyle(
  389 + fontSize: 14,
  390 + color: _getColorByState(state),
  391 + fontWeight: FontWeight.bold,
  392 + ),
  393 + ),
  394 + TextSpan(
  395 + text: '${_getStatusByState(state)} ${_formatNumber(rod.toY)}%',
  396 + style: TextStyle(
  397 + fontSize: 18,
389 color: _getColorByState(state), 398 color: _getColorByState(state),
390 fontWeight: FontWeight.bold, 399 fontWeight: FontWeight.bold,
391 ), 400 ),
@@ -398,9 +407,18 @@ class TodayHrvChartCard extends StatelessWidget { @@ -398,9 +407,18 @@ class TodayHrvChartCard extends StatelessWidget {
398 ), 407 ),
399 ), 408 ),
400 TextSpan( 409 TextSpan(
401 - text: '♥ ${l10n.pressure} ${_formatHour(x.toDouble())}', 410 + text: '♥ ',
402 style: const TextStyle( 411 style: const TextStyle(
403 color: Color(0xFF78787D), 412 color: Color(0xFF78787D),
  413 + fontSize: 14,
  414 + fontWeight: FontWeight.bold,
  415 + ),
  416 + ),
  417 + TextSpan(
  418 + text: '${l10n.realTimePressure} · ${_formatHour(x.toDouble())}',
  419 + style: const TextStyle(
  420 + color: Color(0xFF78787D),
  421 + fontSize: 14,
404 fontWeight: FontWeight.bold, 422 fontWeight: FontWeight.bold,
405 ), 423 ),
406 ), 424 ),
@@ -410,7 +428,7 @@ class TodayHrvChartCard extends StatelessWidget { @@ -410,7 +428,7 @@ class TodayHrvChartCard extends StatelessWidget {
410 List<TextSpan>? _getTooltipChildren(LineBarSpot flSpot, int? state) { 428 List<TextSpan>? _getTooltipChildren(LineBarSpot flSpot, int? state) {
411 return [ 429 return [
412 TextSpan( 430 TextSpan(
413 - text: '○ ${_getStatusByState(state)}', 431 + text: '○ ',
414 style: TextStyle( 432 style: TextStyle(
415 color: _getColorByState(state), 433 color: _getColorByState(state),
416 // fontWeight: FontWeight.bold, 434 // fontWeight: FontWeight.bold,
@@ -418,6 +436,14 @@ class TodayHrvChartCard extends StatelessWidget { @@ -418,6 +436,14 @@ class TodayHrvChartCard extends StatelessWidget {
418 ), 436 ),
419 ), 437 ),
420 TextSpan( 438 TextSpan(
  439 + text: _getStatusByState(state),
  440 + style: TextStyle(
  441 + color: _getColorByState(state),
  442 + // fontWeight: FontWeight.bold,
  443 + fontSize: 18,
  444 + ),
  445 + ),
  446 + TextSpan(
421 text: '\n', 447 text: '\n',
422 style: TextStyle( 448 style: TextStyle(
423 color: Colors.white, 449 color: Colors.white,
@@ -425,7 +451,15 @@ class TodayHrvChartCard extends StatelessWidget { @@ -425,7 +451,15 @@ class TodayHrvChartCard extends StatelessWidget {
425 ), 451 ),
426 ), 452 ),
427 TextSpan( 453 TextSpan(
428 - text: '♥ HRV ${_formatNumber(flSpot.y)}ms · ${_formatHour(flSpot.x)}', 454 + text: '♥ ',
  455 + style: const TextStyle(
  456 + color: Color(0xFF78787D),
  457 + fontSize: 14,
  458 + fontWeight: FontWeight.bold,
  459 + ),
  460 + ),
  461 + TextSpan(
  462 + text: 'HRV ${_formatNumber(flSpot.y)}ms · ${_formatHour(flSpot.x)}',
429 style: const TextStyle( 463 style: const TextStyle(
430 color: Color(0xFF78787D), 464 color: Color(0xFF78787D),
431 fontWeight: FontWeight.bold, 465 fontWeight: FontWeight.bold,
@@ -574,6 +608,7 @@ class _HrvLineChartState extends State<_HrvLineChart> { @@ -574,6 +608,7 @@ class _HrvLineChartState extends State<_HrvLineChart> {
574 ], 608 ],
575 lineTouchData: LineTouchData( 609 lineTouchData: LineTouchData(
576 handleBuiltInTouches: false, 610 handleBuiltInTouches: false,
  611 + touchSpotThreshold: 20.0,
577 touchCallback: 612 touchCallback:
578 (FlTouchEvent event, LineTouchResponse? touchResponse) { 613 (FlTouchEvent event, LineTouchResponse? touchResponse) {
579 if (event is FlPanStartEvent || 614 if (event is FlPanStartEvent ||
@@ -588,6 +623,10 @@ class _HrvLineChartState extends State<_HrvLineChart> { @@ -588,6 +623,10 @@ class _HrvLineChartState extends State<_HrvLineChart> {
588 setState(() { 623 setState(() {
589 _hrvShowingSpots = touchResponse.lineBarSpots!; 624 _hrvShowingSpots = touchResponse.lineBarSpots!;
590 }); 625 });
  626 + } else {
  627 + setState(() {
  628 + _hrvShowingSpots = [];
  629 + });
591 } 630 }
592 } else if (event is FlPanEndEvent || 631 } else if (event is FlPanEndEvent ||
593 event is FlTapUpEvent || 632 event is FlTapUpEvent ||
@@ -700,11 +739,11 @@ class _StressBarChartState extends State<_StressBarChart> { @@ -700,11 +739,11 @@ class _StressBarChartState extends State<_StressBarChart> {
700 Widget build(BuildContext context) { 739 Widget build(BuildContext context) {
701 return LayoutBuilder( 740 return LayoutBuilder(
702 builder: (context, constraints) { 741 builder: (context, constraints) {
703 - // 右轴 reservedSize=22,剩余宽度平分给所有槽  
704 - const rightAxisWidth = 22.0; 742 + // 右轴 reservedSize=22,不占用空间设为 0.0
  743 + const rightAxisWidth = 0.0;
705 const slotSec = 6 * 60; 744 const slotSec = 6 * 60;
706 final slotCount = (widget.maxSeconds / slotSec).ceil().clamp(1, 9999); 745 final slotCount = (widget.maxSeconds / slotSec).ceil().clamp(1, 9999);
707 - const double groupsSpace = 0; // 设置间隔为 1 像素 746 + const double groupsSpace = 0.25; // 设置间隔为 1 像素
708 final barWidth = (constraints.maxWidth - 747 final barWidth = (constraints.maxWidth -
709 rightAxisWidth - 748 rightAxisWidth -
710 (groupsSpace * (slotCount - 1))) / 749 (groupsSpace * (slotCount - 1))) /
@@ -822,11 +861,16 @@ class _StressBarChartState extends State<_StressBarChart> { @@ -822,11 +861,16 @@ class _StressBarChartState extends State<_StressBarChart> {
822 } 861 }
823 } 862 }
824 863
  864 + final chartHeight = constraints.maxHeight;
  865 + const gridTop = 18.0;
  866 + final gridBottom = chartHeight - 20.0;
  867 + final gridHeight = gridBottom - gridTop;
  868 +
825 return Stack( 869 return Stack(
826 clipBehavior: Clip.none, 870 clipBehavior: Clip.none,
827 children: [ 871 children: [
828 - Padding(  
829 - padding: const EdgeInsets.only(top: 6), 872 + Container(
  873 + margin: const EdgeInsets.only(top: gridTop),
830 child: BarChart( 874 child: BarChart(
831 BarChartData( 875 BarChartData(
832 minY: 0, 876 minY: 0,
@@ -868,28 +912,12 @@ class _StressBarChartState extends State<_StressBarChart> { @@ -868,28 +912,12 @@ class _StressBarChartState extends State<_StressBarChart> {
868 sideTitles: SideTitles(showTitles: false)), 912 sideTitles: SideTitles(showTitles: false)),
869 leftTitles: const AxisTitles( 913 leftTitles: const AxisTitles(
870 sideTitles: SideTitles(showTitles: false)), 914 sideTitles: SideTitles(showTitles: false)),
871 - rightTitles: AxisTitles(  
872 - sideTitles: SideTitles(  
873 - showTitles: true,  
874 - interval: 50,  
875 - reservedSize: 22,  
876 - getTitlesWidget: (val, _) {  
877 - if (val < 0 || val > 100) {  
878 - return const SizedBox.shrink();  
879 - }  
880 - return Text(  
881 - '${val.toInt()}',  
882 - style: TextStyle(  
883 - fontSize: 10,  
884 - color: context.colors.textTertiary),  
885 - );  
886 - },  
887 - ),  
888 - ), 915 + rightTitles: const AxisTitles(
  916 + sideTitles: SideTitles(showTitles: false)),
889 bottomTitles: AxisTitles( 917 bottomTitles: AxisTitles(
890 sideTitles: SideTitles( 918 sideTitles: SideTitles(
891 showTitles: true, 919 showTitles: true,
892 - reservedSize: 22, 920 + reservedSize: 20,
893 interval: 6 * 3600, 921 interval: 6 * 3600,
894 getTitlesWidget: (val, meta) { 922 getTitlesWidget: (val, meta) {
895 final label = widget.timeLabel(val); 923 final label = widget.timeLabel(val);
@@ -914,6 +942,8 @@ class _StressBarChartState extends State<_StressBarChart> { @@ -914,6 +942,8 @@ class _StressBarChartState extends State<_StressBarChart> {
914 ), 942 ),
915 barTouchData: BarTouchData( 943 barTouchData: BarTouchData(
916 handleBuiltInTouches: false, 944 handleBuiltInTouches: false,
  945 + touchExtraThreshold: const EdgeInsets.symmetric(
  946 + horizontal: 16, vertical: 40),
917 touchCallback: 947 touchCallback:
918 (FlTouchEvent event, BarTouchResponse? touchResponse) { 948 (FlTouchEvent event, BarTouchResponse? touchResponse) {
919 if (event is FlPanStartEvent || 949 if (event is FlPanStartEvent ||
@@ -928,6 +958,10 @@ class _StressBarChartState extends State<_StressBarChart> { @@ -928,6 +958,10 @@ class _StressBarChartState extends State<_StressBarChart> {
928 _stressTouchedGroupIndex = 958 _stressTouchedGroupIndex =
929 touchResponse.spot!.touchedBarGroupIndex; 959 touchResponse.spot!.touchedBarGroupIndex;
930 }); 960 });
  961 + } else {
  962 + setState(() {
  963 + _stressTouchedGroupIndex = null;
  964 + });
931 } 965 }
932 } else if (event is FlPanEndEvent || 966 } else if (event is FlPanEndEvent ||
933 event is FlTapUpEvent || 967 event is FlTapUpEvent ||
@@ -978,6 +1012,34 @@ class _StressBarChartState extends State<_StressBarChart> { @@ -978,6 +1012,34 @@ class _StressBarChartState extends State<_StressBarChart> {
978 ), 1012 ),
979 ), 1013 ),
980 ...sleepWidgets, 1014 ...sleepWidgets,
  1015 + // 手绘 100、50、0 标签在对应的横线上方
  1016 + Positioned(
  1017 + right: 0,
  1018 + top: gridTop - 12,
  1019 + child: Text(
  1020 + '100',
  1021 + style:
  1022 + TextStyle(fontSize: 10, color: context.colors.textTertiary),
  1023 + ),
  1024 + ),
  1025 + Positioned(
  1026 + right: 0,
  1027 + top: gridTop + gridHeight * 0.5 - 12,
  1028 + child: Text(
  1029 + '50',
  1030 + style:
  1031 + TextStyle(fontSize: 10, color: context.colors.textTertiary),
  1032 + ),
  1033 + ),
  1034 + Positioned(
  1035 + right: 0,
  1036 + top: gridBottom - 12,
  1037 + child: Text(
  1038 + '0',
  1039 + style:
  1040 + TextStyle(fontSize: 10, color: context.colors.textTertiary),
  1041 + ),
  1042 + ),
981 ], 1043 ],
982 ); 1044 );
983 }, 1045 },
@@ -1017,7 +1079,7 @@ class _StressBarChartState extends State<_StressBarChart> { @@ -1017,7 +1079,7 @@ class _StressBarChartState extends State<_StressBarChart> {
1017 color: entry != null 1079 color: entry != null
1018 ? widget.getColorByState(entry.state) 1080 ? widget.getColorByState(entry.state)
1019 : Colors.transparent, 1081 : Colors.transparent,
1020 - borderRadius: BorderRadius.zero, 1082 + borderRadius: BorderRadius.circular(barWidth / 2),
1021 ), 1083 ),
1022 ], 1084 ],
1023 )); 1085 ));
@@ -24,7 +24,7 @@ class TodayHrvNumberCard extends StatelessWidget { @@ -24,7 +24,7 @@ class TodayHrvNumberCard extends StatelessWidget {
24 controller.selectedDate.value, controller.lastSelectableDay); 24 controller.selectedDate.value, controller.lastSelectableDay);
25 25
26 var otherHrv = (sameDay 26 var otherHrv = (sameDay
27 - ? context.l10n.todaySHrvTrend 27 + ? context.l10n.todaySAverageHrv
28 : context.l10n.averageHrvForTheDay); 28 : context.l10n.averageHrvForTheDay);
29 return Row( 29 return Row(
30 children: [ 30 children: [
@@ -53,7 +53,6 @@ class TodaySleepCard extends StatelessWidget { @@ -53,7 +53,6 @@ class TodaySleepCard extends StatelessWidget {
53 color: context.colors.textPrimary, 53 color: context.colors.textPrimary,
54 fontSize: 16, 54 fontSize: 16,
55 fontWeight: FontWeight.w600, 55 fontWeight: FontWeight.w600,
56 - height: 19 / 16,  
57 ), 56 ),
58 children: [ 57 children: [
59 if (sleepHours > 0) ...[ 58 if (sleepHours > 0) ...[
@@ -87,7 +86,6 @@ class TodaySleepCard extends StatelessWidget { @@ -87,7 +86,6 @@ class TodaySleepCard extends StatelessWidget {
87 color: context.colors.textPrimary, 86 color: context.colors.textPrimary,
88 fontSize: 16, 87 fontSize: 16,
89 fontWeight: FontWeight.w600, 88 fontWeight: FontWeight.w600,
90 - height: 19 / 16,  
91 ), 89 ),
92 )), 90 )),
93 _MetricBlock( 91 _MetricBlock(
@@ -99,7 +97,6 @@ class TodaySleepCard extends StatelessWidget { @@ -99,7 +97,6 @@ class TodaySleepCard extends StatelessWidget {
99 color: sleepStateColor, 97 color: sleepStateColor,
100 fontSize: 16, 98 fontSize: 16,
101 fontWeight: FontWeight.w600, 99 fontWeight: FontWeight.w600,
102 - height: 19 / 16,  
103 ), 100 ),
104 ), 101 ),
105 ), 102 ),
@@ -113,7 +110,6 @@ class TodaySleepCard extends StatelessWidget { @@ -113,7 +110,6 @@ class TodaySleepCard extends StatelessWidget {
113 color: context.colors.textPrimary, 110 color: context.colors.textPrimary,
114 fontSize: 16, 111 fontSize: 16,
115 fontWeight: FontWeight.w600, 112 fontWeight: FontWeight.w600,
116 - height: 19 / 16,  
117 ), 113 ),
118 children: [ 114 children: [
119 TextSpan(text: sleepAverageHeartRate), 115 TextSpan(text: sleepAverageHeartRate),
@@ -134,7 +130,6 @@ class TodaySleepCard extends StatelessWidget { @@ -134,7 +130,6 @@ class TodaySleepCard extends StatelessWidget {
134 color: context.colors.textPrimary, 130 color: context.colors.textPrimary,
135 fontSize: 16, 131 fontSize: 16,
136 fontWeight: FontWeight.w600, 132 fontWeight: FontWeight.w600,
137 - height: 19 / 16,  
138 ), 133 ),
139 ), 134 ),
140 ), 135 ),
@@ -196,7 +191,6 @@ class TodayActivityCard extends StatelessWidget { @@ -196,7 +191,6 @@ class TodayActivityCard extends StatelessWidget {
196 color: context.colors.textPrimary, 191 color: context.colors.textPrimary,
197 fontSize: 16, 192 fontSize: 16,
198 fontWeight: FontWeight.w600, 193 fontWeight: FontWeight.w600,
199 - height: 19 / 16,  
200 ), 194 ),
201 children: [ 195 children: [
202 if (exHours > 0) ...[ 196 if (exHours > 0) ...[
@@ -231,7 +225,6 @@ class TodayActivityCard extends StatelessWidget { @@ -231,7 +225,6 @@ class TodayActivityCard extends StatelessWidget {
231 color: context.colors.textPrimary, 225 color: context.colors.textPrimary,
232 fontSize: 16, 226 fontSize: 16,
233 fontWeight: FontWeight.w600, 227 fontWeight: FontWeight.w600,
234 - height: 19 / 16,  
235 ), 228 ),
236 children: [ 229 children: [
237 const TextSpan(text: '-'), 230 const TextSpan(text: '-'),
@@ -249,7 +242,6 @@ class TodayActivityCard extends StatelessWidget { @@ -249,7 +242,6 @@ class TodayActivityCard extends StatelessWidget {
249 color: context.colors.textPrimary, 242 color: context.colors.textPrimary,
250 fontSize: 16, 243 fontSize: 16,
251 fontWeight: FontWeight.w600, 244 fontWeight: FontWeight.w600,
252 - height: 19 / 16,  
253 ), 245 ),
254 children: [ 246 children: [
255 TextSpan(text: '$stand'), 247 TextSpan(text: '$stand'),
@@ -271,7 +263,6 @@ class TodayActivityCard extends StatelessWidget { @@ -271,7 +263,6 @@ class TodayActivityCard extends StatelessWidget {
271 color: context.colors.textPrimary, 263 color: context.colors.textPrimary,
272 fontSize: 16, 264 fontSize: 16,
273 fontWeight: FontWeight.w600, 265 fontWeight: FontWeight.w600,
274 - height: 19 / 16,  
275 ), 266 ),
276 children: [ 267 children: [
277 const TextSpan(text: '-'), 268 const TextSpan(text: '-'),
@@ -306,7 +297,6 @@ class TodayActivityCard extends StatelessWidget { @@ -306,7 +297,6 @@ class TodayActivityCard extends StatelessWidget {
306 color: context.colors.textPrimary, 297 color: context.colors.textPrimary,
307 fontSize: 16, 298 fontSize: 16,
308 fontWeight: FontWeight.w600, 299 fontWeight: FontWeight.w600,
309 - height: 19 / 16,  
310 ), 300 ),
311 children: [ 301 children: [
312 TextSpan(text: activityStr), 302 TextSpan(text: activityStr),
@@ -427,7 +417,6 @@ class _TodaySummaryCard extends StatelessWidget { @@ -427,7 +417,6 @@ class _TodaySummaryCard extends StatelessWidget {
427 color: colors.textPrimary, 417 color: colors.textPrimary,
428 fontSize: 14, 418 fontSize: 14,
429 fontWeight: FontWeight.w500, 419 fontWeight: FontWeight.w500,
430 - height: 20 / 14,  
431 ), 420 ),
432 ), 421 ),
433 const Spacer(), 422 const Spacer(),
@@ -437,7 +426,6 @@ class _TodaySummaryCard extends StatelessWidget { @@ -437,7 +426,6 @@ class _TodaySummaryCard extends StatelessWidget {
437 color: colors.textSecondary, 426 color: colors.textSecondary,
438 fontSize: 12, 427 fontSize: 12,
439 fontWeight: FontWeight.w400, 428 fontWeight: FontWeight.w400,
440 - height: 17 / 12,  
441 ), 429 ),
442 ), 430 ),
443 Image.asset( 431 Image.asset(
@@ -495,7 +483,6 @@ class _MetricBlock extends StatelessWidget { @@ -495,7 +483,6 @@ class _MetricBlock extends StatelessWidget {
495 color: labelColor, 483 color: labelColor,
496 fontSize: 12, 484 fontSize: 12,
497 fontWeight: FontWeight.w500, 485 fontWeight: FontWeight.w500,
498 - height: 17 / 12,  
499 ), 486 ),
500 ), 487 ),
501 const SizedBox(height: 5), 488 const SizedBox(height: 5),
@@ -351,11 +351,12 @@ class HealthApi { @@ -351,11 +351,12 @@ class HealthApi {
351 ); 351 );
352 } 352 }
353 353
354 - Future<AppResult<bool>> getHealthDataEverUploaded() { 354 + Future<AppResult<V2HealthEverUploaded>> getHealthDataEverUploaded() {
355 return safeCall( 355 return safeCall(
356 call: () async { 356 call: () async {
357 final response = await _dioClient.dio.get(ApiPaths.weatherUploadedData); 357 final response = await _dioClient.dio.get(ApiPaths.weatherUploadedData);
358 - return response.data['flag']; 358 + return V2HealthEverUploaded.fromJson(
  359 + response.data as Map<String, dynamic>);
359 }, 360 },
360 errorHandlingPolicy: HttpErrorHandlingPolicy.defaultPolicy, 361 errorHandlingPolicy: HttpErrorHandlingPolicy.defaultPolicy,
361 ); 362 );
@@ -55,9 +55,9 @@ class V2LatestHrvData { @@ -55,9 +55,9 @@ class V2LatestHrvData {
55 case 2: 55 case 2:
56 return context.colors.chartOrange; 56 return context.colors.chartOrange;
57 case 3: 57 case 3:
58 - return context.colors.chartGreen;  
59 - case 4:  
60 return context.colors.chartBlue; 58 return context.colors.chartBlue;
  59 + case 4:
  60 + return context.colors.chartGreen;
61 } 61 }
62 return context.colors.chartGreen; 62 return context.colors.chartGreen;
63 } 63 }
@@ -437,3 +437,27 @@ class V2ActivityTarget { @@ -437,3 +437,27 @@ class V2ActivityTarget {
437 return val; 437 return val;
438 } 438 }
439 } 439 }
  440 +
  441 +class V2HealthEverUploaded {
  442 + const V2HealthEverUploaded({
  443 + this.flag,
  444 + this.startTime,
  445 + });
  446 +
  447 + final bool? flag;
  448 + final int? startTime;
  449 +
  450 + factory V2HealthEverUploaded.fromJson(Map<String, dynamic> json) {
  451 + return V2HealthEverUploaded(
  452 + flag: json['flag'] as bool?,
  453 + startTime: _parseInt(json['start_time']),
  454 + );
  455 + }
  456 +
  457 + Map<String, dynamic> toJson() {
  458 + return {
  459 + 'flag': flag,
  460 + 'start_time': startTime,
  461 + };
  462 + }
  463 +}
@@ -562,5 +562,6 @@ @@ -562,5 +562,6 @@
562 "freeRedemptionOffer": "Free Redemption Offer", 562 "freeRedemptionOffer": "Free Redemption Offer",
563 "cellPhoneNumber": "Cell phone number", 563 "cellPhoneNumber": "Cell phone number",
564 "todayOnWeeklyCalendar": "today", 564 "todayOnWeeklyCalendar": "today",
565 - "hrvTrendForThatDay": "HRV Trend for That Day" 565 + "hrvTrendForThatDay": "HRV Trend for That Day",
  566 + "todaySAverageHrv": "Today's Average HRV"
566 } 567 }
@@ -850,5 +850,6 @@ @@ -850,5 +850,6 @@
850 "freeRedemptionOffer": "免费兑换优惠", 850 "freeRedemptionOffer": "免费兑换优惠",
851 "cellPhoneNumber": "手机号", 851 "cellPhoneNumber": "手机号",
852 "todayOnWeeklyCalendar": "今", 852 "todayOnWeeklyCalendar": "今",
853 - "hrvTrendForThatDay": "该日HRV趋势" 853 + "hrvTrendForThatDay": "该日HRV趋势",
  854 + "todaySAverageHrv": "今日平均HRV"
854 } 855 }
@@ -3332,6 +3332,12 @@ abstract class AppLocalizations { @@ -3332,6 +3332,12 @@ abstract class AppLocalizations {
3332 /// In zh, this message translates to: 3332 /// In zh, this message translates to:
3333 /// **'该日HRV趋势'** 3333 /// **'该日HRV趋势'**
3334 String get hrvTrendForThatDay; 3334 String get hrvTrendForThatDay;
  3335 +
  3336 + /// No description provided for @todaySAverageHrv.
  3337 + ///
  3338 + /// In zh, this message translates to:
  3339 + /// **'今日平均HRV'**
  3340 + String get todaySAverageHrv;
3335 } 3341 }
3336 3342
3337 class _AppLocalizationsDelegate 3343 class _AppLocalizationsDelegate
@@ -1856,4 +1856,7 @@ class AppLocalizationsEn extends AppLocalizations { @@ -1856,4 +1856,7 @@ class AppLocalizationsEn extends AppLocalizations {
1856 1856
1857 @override 1857 @override
1858 String get hrvTrendForThatDay => 'HRV Trend for That Day'; 1858 String get hrvTrendForThatDay => 'HRV Trend for That Day';
  1859 +
  1860 + @override
  1861 + String get todaySAverageHrv => 'Today\'s Average HRV';
1859 } 1862 }
@@ -1757,4 +1757,7 @@ class AppLocalizationsZh extends AppLocalizations { @@ -1757,4 +1757,7 @@ class AppLocalizationsZh extends AppLocalizations {
1757 1757
1758 @override 1758 @override
1759 String get hrvTrendForThatDay => '该日HRV趋势'; 1759 String get hrvTrendForThatDay => '该日HRV趋势';
  1760 +
  1761 + @override
  1762 + String get todaySAverageHrv => '今日平均HRV';
1760 } 1763 }